181

I have this module routes:

var mainModule = angular.module('lpConnect', []).
    config(['$routeProvider', function ($routeProvider) {
    $routeProvider.
        when('/home', {template:'views/home.html', controller:HomeCtrl}).
        when('/admin', {template:'views/admin.html', controller:AdminCtrl}).
        otherwise({redirectTo:'/connect'});
}]);

Home HTML:

<div ng-include src="views.partial1"></div>

partial1 HTML:

<form ng-submit="addLine()">
    <input type="text" ng-model="lineText" size="30" placeholder="Type your message here">
</form>

HomeCtrl:

function HomeCtrl($scope, $location, $window, $http, Common) {
    ...
    $scope.views = {
        partial1:"views/partial1.html"
    };

    $scope.addLine = function () {
        $scope.chat.addLine($scope.lineText);
        $scope.lines.push({text:$scope.lineText});
        $scope.lineText = "";
    };
...
}

In the addLine function $scope.lineText is undefined, this can be resolved by adding ng-controller="HomeCtrl" to partial1.html, however it causes the controller to be called twice. What am I missing here?

Sebastian Simon
  • 14,320
  • 6
  • 42
  • 61
Shlomi Schwartz
  • 11,238
  • 25
  • 93
  • 155

4 Answers4

260

As @Renan mentioned, ng-include creates a new child scope. This scope prototypically inherits (see dashed lines below) from the HomeCtrl scope. ng-model="lineText" actually creates a primitive scope property on the child scope, not HomeCtrl's scope. This child scope is not accessible to the parent/HomeCtrl scope:

ng-include scope

To store what the user typed into HomeCtrl's $scope.lines array, I suggest you pass the value to the addLine function:

 <form ng-submit="addLine(lineText)">

In addition, since lineText is owned by the ngInclude scope/partial, I feel it should be responsible for clearing it:

 <form ng-submit="addLine(lineText); lineText=''">

Function addLine() would thus become:

$scope.addLine = function(lineText) {
    $scope.chat.addLine(lineText);
    $scope.lines.push({
        text: lineText
    });
};

Fiddle.

Alternatives:

  • define an object property on HomeCtrl's $scope, and use that in the partial: ng-model="someObj.lineText; fiddle
  • not recommended, this is more of a hack: use $parent in the partial to create/access a lineText property on the HomeCtrl $scope:  ng-model="$parent.lineText"; fiddle

It is a bit involved to explain why the above two alternatives work, but it is fully explained here: What are the nuances of scope prototypal / prototypical inheritance in AngularJS?

I don't recommend using this in the addLine() function. It becomes much less clear which scope is being accessed/manipulated.

Community
  • 1
  • 1
Mark Rajcok
  • 348,511
  • 112
  • 482
  • 482
  • I used **objects**, but they scope was still masked. I tried `$parent.` and it worked great. Why do you consider it a hack? (I can see it would add maintenance if you refactored your html) – Jess Sep 25 '13 at 16:08
  • 1
    Same question @Jess had , why is this considered a hack ? – qbert65536 Dec 20 '13 at 18:29
  • 13
    @qbert65536, it is essentially a hack/fragile because if you restructure your HTML, it might not work anymore. E.g., you might then need to use `$parent.$parent...` to get it to work. Put another way, using `$parent` makes assumptions about the DOM structure. – Mark Rajcok Dec 20 '13 at 22:43
  • I was having the same problem! Thanks for the detailed explanation – Chris Yeung Mar 17 '14 at 15:40
  • 6
    @Jess' link above has been changed to this __[Understanding Scopes ngInclude](https://github.com/angular/angular.js/wiki/Understanding-Scopes#ng-include)__. Read the whole page, it's great. – mraaroncruz Jun 08 '14 at 15:18
  • Thank you for this. I think this should be the accepted answer. Using `this` feels like a hack in Angular terms – grammar Sep 15 '14 at 16:23
  • I has going crazy about this. Thank for the detailed explanation @MarkRajcok! +1 – Isaias Soares Jun 26 '15 at 11:16
  • 1
    This is a great detailed answer, but I tried all of them without any success. I have a form with some input to a controller and the result of a controller should be viewed on another div. Once I enter any input the synchronicity will be lost and I will have a constant 0.00 value on the view div while the app is running. – zahra Mar 31 '16 at 04:53
83

This is because of ng-include which creates a new child scope, so $scope.lineText isn’t changed. I think that this refers to the current scope, so this.lineText should be set.

Sebastian Simon
  • 14,320
  • 6
  • 42
  • 61
Renan Tomal Fernandes
  • 10,928
  • 4
  • 46
  • 30
34

Instead of using this as the accepted answer suggests, use $parent instead. So in your partial1.htmlyou'll have:

<form ng-submit="$parent.addLine()">
    <input type="text" ng-model="$parent.lineText" size="30" placeholder="Type your message here">
</form>

If you want to learn more about the scope in ng-include or other directives, check this out: https://github.com/angular/angular.js/wiki/Understanding-Scopes#ng-include

goerwin
  • 1,201
  • 9
  • 20
  • 1
    For any reader, he means `$scope.$parent` instead of `$parent` is undefined according to Angular. – Sebastialonso May 21 '15 at 20:09
  • 1
    This answer saves the day for me! Thanks so much for pointing out the use of $parent. – Derek Webb May 31 '15 at 01:40
  • is $scope.$parent pass by reference? or it's just a copy of parent? – OMGPOP Jun 25 '15 at 12:01
  • 1
    @Sebastiallonso is wrong. $scope.$parent.lineText is undefined. $parent.lineText is working, this.lineText or simply just lineText are also working – OMGPOP Jun 25 '15 at 12:09
  • Its `$scope.$parent` that works for me in angular 1.3.20 – radtek Mar 10 '16 at 08:43
  • @Sebastialonso is right and so is OMGPOP: in the directive or html $scope.$parent is $parent if you are referring to it inside a controller it is $scope (or in a directive typically 'scope').$parent... Inside html $scope is already passed so it would make it look like $parent is right but officially it is $scope.$parent – Zargold Mar 27 '16 at 03:40
4

I've figured out how to work around this issue without mixing parent and sub scope data. Set a ng-if on the the ng-include element and set it to a scope variable. For example :

<div ng-include="{{ template }}" ng-if="show"/>

In your controller, when you have set all the data you need in your sub scope, then set show to true. The ng-include will copy at this moment the data set in your scope and set it in your sub scope.

The rule of thumb is to reduce scope data deeper the scope are, else you have this situation.

Max

wascou
  • 89
  • 2
  • I'm using this approach for a similar issue but it's not proper for all cases. Especially when you want the included element to never get hidden... – iSpithash Dec 10 '19 at 09:18