In a child controller, I'm populating an array on the parent scope:
$scope.person.testArray = [{id: "test"},{id: "test2"}];
That works fine and the person property now has an extra property called testArray, which contains the above values.
Then in the view associated with the child controller, I want to display the values of the array:
<div ng-repeat="op in person.testArray">
{{op.id}}
</div>
Nothing shows up.
However, I know it's related to the fact that I'm using ng-repeat. If, in the child controller I set a regular property on the person object, such as:
$scope.person.test = "test";
and then in the view I do:
{{person.test}}
it shows the string "test".
What am I doing wrong with ng-repeat ?
You shouldn't have to use $parent in your child controller, since the child controller's $scope prototypically inherits from the parent controller's $scope:
function ParentCtrl($scope) {
$scope.person = {};
}
function ChildCtrl($scope) {
$scope.person.testArray = [{id: "test"}, {id: "test2"}];
}
HTML:
<div ng-controller="ParentCtrl">
<div ng-controller="ChildCtrl">
<div ng-repeat="op in person.testArray">{{op.id}}</div>
</div>
</div>
fiddle
While like the others I suggest against using $parent, you should be able to use the ng-repeat like this:
<div ng-repeat="op in $parent.person.testArray">
{{op.id}}
</div>
Related
I'm building a single page web app using AngularJS with ui-router. I have two different states, one parent and one child. In the parent state, 'spots', users can make a selection from an ng-repeat and their selection is shown using the scope.
When a user makes the selection, I have ng-click fire a function which uses $state.go to load the child state 'details'. I would like to load their selection in the child state, but it appears that the scope data is gone?
I've tried using the same controller for each state. ui-sref doesn't work either.
From the parent state HTML template
<div class="card-column mx-0" data-ng-click="makeSelection = true">
<div class="card mx-0 mb-3 ng-scope" data-ng-click="showSpot(spot);" data-ng-repeat="spot in spots | filter:{'game':gameID} | filter:{'walking':distanceID} | filter:{'vehicle':vehicleID} | orderBy:'price' | filter as results">
<div class="row no-gutters">
<div class="col-sm-12 col-md-3 col-lg-3">
<img src="{{ spot.image }}" alt="parking spot"/>
</div>
<div class="col-sm-12 col-md-9 col-lg-9">
<div class="card-body px-4 pt-4">
<h6 class="text-small-extra text-muted font-weight-normal text-uppercase"><span style="letter-spacing: .05rem;">{{ spot.type }}</span></h6>
<h5 class="card-title">{{ spot.address }}</h5>
<h4 class="text-muted float-md-right">${{ spot.price }}<span style="font-size: 1rem; font-weight: 400">/day</span></h4>
</div>
</div>
</div>
</div>
Snippet from the controller
$scope.showDetails = function() {
$state.go('spots.details'); //my route...
}
$scope.showSpot = function(spot) {
$scope.spot = spot;
$scope.showDetails();
}
Snippet from app.js
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/")
$stateProvider
.state('spots',{
url: '/',
templateUrl: "/parkit/master/spots-available.html",
controller: 'parkitController'
})
.state('details', {
parent: 'spots',
url: '/details',
templateUrl: '/parkit/master/details.html',
})
.state('statetwo', {
url: '/statetwo',
template: '<h1>State Two</h1>',
controller: 'parkitController'
});
})
I expected the user selection to show on the child state after ng-click is fired.
You need to under stand how prototypal inheritance works. When a parent puts a property value on the scope with
$scope.value = 'something';
In a child component if you access $scope.value the inheritance chain will find $scope.value.
If the child sets
$scope.otherValue = 'something';
If follows the inheritance chain, doesn't find a value of otherValue and creates a property on the child scope, not the inherited prototype so the parent component and any other children of the parent do not see it.
You can use what is called the dot rule of prototypal inheritance. If the parent creates an object on the scope called something like data
$scope.data = { value: 'something' };
Now if the child puts a property on the data object
$scope.data.otherValue = 'something';
It looks for the data object, finds it in the inheritence chain and because you are adding a property to an instance of an object it is visible to the parent and any children of the parent.
let parent = {
value: 'some value',
data: { value: 'some value' }
};
let child = Object.create(parent);
console.log(child.value); // Finds value on the prototype chain
child.newValue = 'new value'; // Does not affect the parent
console.log(parent.newValue);
child.data.newValue = 'new value'; // newValue is visible to the parent
console.log(parent.data.newValue);
Short answer is to just never inject $scope and use controllerAs syntax.
To share data between controllers you use a service that is injected to both controllers. You have the spots collection on the service and use a route param to identify which spot the other controller should use or have a place on the service called currentSpot set by the other controller.
Services are a singleton object that you create at the module level and then all controllers that ask for them in their dependency list get the same instance. They are the preferred way to share data between controllers, $scope hierarchies are bound to lead to confusion as the prototypal inheritance nature of them can be confusing. A child $scope is prototypally inherited from it's parent, this seems like you should be sharing data but when a child controller sets a property it is not visible to the parent.
You are learning an outdated way of Angular programming. Injecting $scope is no longer a recommended way. Look at using components. Components are a wrapper for a controller with an isolated scope and using contollerAs syntax. Isolated scopes make it much cleaner to know where data comes from.
Take a look at my answer on this question
Trying to activate a checkbox from a controller that lives in another controller
the ng-repeat outputs nothing. in the link function, i can see $scope.clients is an array. if i remove the isolate scope, and use the parent scope, the ng-repeat works.
html with directive "clients".
<div container
ng-cloak
ng-app="summaryReportApp"
ng-controller="summaryReportController as summaryReport">
<fieldset clients="summaryReport.clients">
<legend>Clients</legend>
<div align="left">
<div ng-repeat="client in clients track by $index">
{{client}}
</div>
</div>
</fieldset>
</div>
directive
var clients = function(){
var definition = {
restrict: "A",
scope: {
clients:"=clients"
},
link: function($scope,$element,attributes){
}
}
return definition;
}
This is a common question I seem to answer frequently. Directives can have other HTML Elements nested in them, in the same way that an <input> can be nested inside a <div>. However, the Elements nested inside the Directive are not part of the directive, and are not scoped to the directive, they are scoped to the HTML they are in. The only items that have access to the Isolated Scope are the compile, link, controller, and template items in the directive definition. If you moved your inner html from inside the fieldset into a template, it would function as expected.
You can also reference http://angular-tips.com/blog/2014/03/transclusion-and-scopes/ for more examples and ways to test this.
The following could be run in demo here.
this is html:
<div ng-controller="MyCtrl">
<h2>Parent Scope</h2>
<input ng-model="foo"> <i>// Update to see how parent scope interacts with component scope</i>
<br><br>
<!-- attribute-foo binds to a DOM attribute which is always
a string. That is why we are wrapping it in curly braces so
that it can be interpolated.
-->
<my-component attribute-foo="{{foo}}" binding-foo="foo"
isolated-expression-foo="updateFoo(newFoo)" >
<h2>Attribute</h2>
<div>
<strong>get:</strong> {{isolatedAttributeFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedAttributeFoo">
<i>// This does not update the parent scope.</i>
</div>
<h2>Binding</h2>
<div>
<strong>get:</strong> {{isolatedBindingFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedBindingFoo">
<i>// This does update the parent scope.</i>
</div>
<h2>Expression</h2>
<div>
<input ng-model="isolatedFoo">
<button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>
<i>// And this calls a function on the parent scope.</i>
</div>
</my-component>
</div>
And this is js:
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'#attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.foo = 'Hello!';
$scope.updateFoo = function (newFoo) {
$scope.foo = newFoo;
}
}]);
This should be a good example for three kinds of scope binding in directives.However, it just doesn't work when I try to switch a higher angular version - (1.2.27). I suspect the shadow of the inherited scope within the directive, but I'm not sure of it.
This isn't going to work the way you expect. Isolated Scopes are created and provided to the Link, Compile, and Template portions of a Directive. However, the HTML within the Element itself is not actually part of the Directive. Those HTML portions are still bound to the parent $scope. If you have a tendancy to name your isolated scope objects the same, you may have just been working against the $scope unintentionally and not noticed any ill effect. If your HTML was in a Template rather than inside the Element, it would access the isolate scope.
As an example, in the HTML that is inline in the Element, you can call updateFoo(), but that would not be possible from inside a Template
So I have below index.html:
<div ng-controller="UsersController">
<div ng-include='"assets/users/partials/template.html"'></div>
<a ng-click="get_data()">Get</a>
</div>
Template.html:
<input type="text" ng-model="SearchUser" name="SearchUser" />
My controller:
app.controller('UsersController', ['$scope', function($scope) {
$scope.get_data = function(){ console.log($scope.SearchUser); };
}
]);
So in above case on the click anchor, I am getting undefined in the $scope.SearchUser scope value.
But if I take that input out of the template and put inside main HTML it works.
I checked for multiple controller declaration and other stuffs but nothing worked for me.
I am using angular 1.2.25 version.
ng-include defines its own scope, which inherits from the controller scope. So SearchUser is set, but as an attribute of the child scope.
As always, the solution is to have a dot in your ng-model, and to define the outer object in the controller scope:
$scope.state = {};
and, in the HTML:
<input type="text" ng-model="state.SearchUser" name="SearchUser" />
That way, angular will get the state field from the child scope. Since the child scope prototypically extends the controller scope, it will find it in the controller scope, and it will write the SearchUser attribute of the state object.
I want to use the angular's ng-repeat filter like so:
<div ng-repeat="trade in trades | filter:searchTrades | orderBy:predicate:reverse">
the problem here is the input control where I want to bind "searchTrades" to exists OUTSIDE the controller and view where the ng-repeate exists. the input field exists outside the controller for a good reason. it's a global search input that i intend to use differently with each controller. so further more I will need to give the search input different behavior depending on which controller/view is active.
This is a question of scopes, and eventing between scopes. As angular uses prototype inheritance, you can still gain access to "parent" scope properties and react to them.
The short of it, if you have searchTrades on a parent controller, the child controller can access it. Note if the child controller modifies searchTrades it will make a "new copy", if you need to do that use $scope.$emit and $scope.$on
Here is a plunker to look at
Consider the following
Controllers
function MainCtrl($scope, ...) {
$scope.search = 'My search term'
}
function ChildCtrl1($scope, ...) {
$scope.items = ['Foo', ... ]
}
View
<body ng-controller="MainCtrl">
<label>Search</label> <input ng-model="search" />
<div ng-controller="ChildCtrl1">
<ul>
<li ng-repeat="item in items | filter:search">{{item}}</li>
</ul>
</div>
</body>
ChildCtrl1 will inherit search from the parent controller, and it can be used as "normal"