AngularJS, nested controllers from variable - javascript

Generally, what I want to do, is to initialize nested ng-controller inside ng-repeat using variable.
JSFiddle
JS
angular.module('app',[])
.controller('main',function($scope){
angular.extend($scope,{
name:'Parent Controller',
items:[
{name:'nested2'},
{name:'nested1'}
]
});
})
.controller('nested1',function($scope){
$scope.name = "Name1";
})
.controller('nested2',function($scope){
$scope.name = "Name2";
});
I want this:
<div ng-controller="main" ng-app='app'>
Nested: {{name}}
<div ng-controller="nested1">{{name}}</div>
<div ng-controller="nested2">{{name}}</div>
</div>
to become to something like this:
<div ng-controller="main">
Nested: {{name}}
<div ng-repeat="item in items">
<div ng-controller="item.name">{{name}}</div>
</div>
</div>
Problem: it does not work this way. Neither it works any other way, that I've tried after google'ing for an hour or so.
Is there any "legal" and nice way to achieve that at all?

There isn't a real way,using angular features it at this point, i suppose. You could create a directive and use un-documented dynamic controller feature controller:'#', name:'controllerName'. But this approach will not evaluate bindings or expressions that provide the controller name. What i can think of is a hack by instantiating a controller provided and setting it to the element.
Example:-
.directive('dynController', function($controller){
return {
scope:true, //create a child scope
link:function(scope, elm, attrs){
var ctrl =scope.$eval(attrs.dynController); //Get the controller
///Instantiate and set the scope
$controller(ctrl, {$scope:scope})
//Or you could so this well
//elm.data('$ngControllerController', $controller(ctrl, {$scope:scope}) );
}
}
});
And in your view:-
<div ng-controller="main">
<div ng-repeat="item in items">
Nested:
<div dyn-controller="item.name" ng-click="test()">{{name}}</div>
</div>
</div>
Demo
Note that i have changed the position of ng-controller from the element that does ng-repeat, since ng-repeat (1000) has higher priority than ng-controller (500), ng-repeat's scope will prevail and you end up not repeating anything.
While looking at it

Invested more couple hours into it, inspecting angular's sources etc.
The expression, given to ng-controller, is not being evaluated at all.
Here is best approach I've found:
HTML:
<div ng-controller="main">
Nested: {{name}}
<div ng-repeat="item in items">
<div ng-controller="nested" ng-init="init(item)">{{name}}</div>
</div>
</div>
JS:
angular.module('myApp', [])
.controller('main', function ($scope, $controller) {
angular.extend($scope, {
name: 'Parent Controller',
items: [
{name: 'nested2'},
{name: 'nested1'}
]
});
})
.controller('nested', function ($scope, $controller) {
angular.extend($scope, {
init: function (item) {
$controller(item.name, {'$scope': $scope});
}
});
})
.controller('nested1', function ($scope) {
$scope.name = 'test1';
})
.controller('nested2', function ($scope) {
$scope.name = 'test2';
});

Related

Strange behavior using multiple transclude component in Angularjs 1.6.1

I've a strange behavior using multiple transclude component in Angularjs:
changes in first slot model no visible in controller.
<div ng-app="myApp" ng-controller="testController">
<script type="text/ng-template" id="component-template.html">
<div style="color:red;" ng-transclude="heading">
</div>
<div style="color:blue;" ng-transclude="body">
</div>
</script>
Example1
<input ng-model="example1Model"/>
<test-component>
<panel-heading>
Example2
<input ng-model="example2Model"/>
</panel-heading>
<panel-body>
Example1Result:{{example1Model}}<br/>
Example2Result:{{example2Model}}
</panel-body>
</test-component>
</div>
<script>
angular.module("myApp", [])
.controller("testController", function ($scope, $location) {
})
.component("testComponent", {
templateUrl: "component-template.html",
transclude: {
heading: "panelHeading",
body: "panelBody"
},
controller: function ($scope, $element, $attrs) {
this.$doCheck = function () {
//do anything
}
}
});
</script>
Now if you try to test it in this JSfiddle:https://jsfiddle.net/Lpveophe/1/
Why binding model example2Model did not working?
However example1Model binding model working correctly.
You need to use . in ng-model to make it work. For better understand of scope, please go through this article: https://github.com/angular/angular.js/wiki/Understanding-Scopes
To make it work, replace example2Model with o.example2Model everywhere and change your controller to:
.controller("testController", function ($scope, $location) {
$scope.o = {};
$scope.o.example2Model = '';
})

Angular services giving "TypeError: Cannot read property 'helloConsole' of undefined"

I'm studying AngularJS Services and I'm having a problem.
That's my Angular code:
var app = angular.module("todoListApp");
app.controller('MainController', MainController);
MainController.$inject = ['$scope'];
function MainController($scope, dataService){
$scope.helloConsole = dataService.helloConsole;
};
app.service('dataService', function(){
this.helloConsole = function(){
console.log("console services");
};
});
That's my HTML Code
<div ng-controller="MainController" class="list">
<div class="item" ng-class="{'editing-item' : editing, 'edited': todo.edited}" ng-repeat="todo in todos">
<div class="actions">
Edit
Save
Delete
</div>
</div>
</div>
I'm trying to make it so that when I click on Save, the console shows me "console services", but it's giving me an error:
angular.js:13424 TypeError: Cannot read property 'helloConsole' of undefined
Proper Angular Structure
you need to change the way you have written your code. It should look more like this
angular.module("todoListApp", [])
.controller('MainController', ['$scope', 'dataService', function($scope, dataService){
$scope.helloConsole = dataService.helloConsole;
}])
.service('dataService', [function(){
this.helloConsole = function(){
console.log("console services");
};
}]);
Also this is a 'data service' is this gettig data with a http call? Because if so then make a factory.
Controllers for business logic
Factories for data requests
Services for things like login
Directives for DOM manipulation
Filters for format
Return a singleton service object from angular.service's second function argument. Also, if you're explicit about the dependencies of your controller, thinks will work a lot clearer/better:
var app = angular.module("todoListApp", []);
app.controller('MainController', ['$scope', 'dataService', MainController]);
function MainController($scope, dataService){
$scope.helloConsole = dataService.helloConsole;
$scope.todos = [{txt:"todo 1"}, {txt:"todo 2"}];
}
app.service('dataService', function(){
return {
helloConsole: function(){
console.log("console services");
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.js"></script>
<div ng-app="todoListApp">
<div ng-controller="MainController" class="list">
<div class="item" ng-class="{'editing-item' : editing, 'edited': todo.edited}" ng-repeat="todo in todos">
{{todo.txt}}
<div class="actions">
Edit
Save
Delete
</div>
</div>
</div>
</div>
I rewrote this a little bit so it's easier to understand (for me at least).
I have added a "todos" array to your code just to make the ng-repeat fire.
var app = angular.module("todoListApp",[])
.service('dataService', function(){
this.helloConsole = function(){
console.log("console services");
};
})
.controller('MainController', [ '$scope', 'dataService',function ($scope,dataService) {
$scope.helloConsole = dataService.helloConsole;
$scope.todos = [ {
"todo":"1"
}
]
}])
;

$scope.item in directive is undefined

I have a problem with my directive and controller. The variable item in scope is undefined in directive, even though I passed it in html. This is my code:
app.js:
var app = angular.module("app", ["ngRoute"]);
app.config(["$routeProvider", function($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "views/main.html",
controller: "MainCtrl"
});
}]);
controller.js:
app.controller("MainCtrl", function($scope) {
$scope.item = "x";
});
directive.js:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
templateUrl: "views/item.html"
};
});
index.html:
<div ng-view></div>
main.html:
<div example-directive item="item"></div>
item.html:
<div>{{ item }}</div>
UPDATE
I changed my code to:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: true,
templateUrl: "views/item.html"
};
});
and now there is "x" in scope.$parent.item. But why it isn't present inside directive?
Although it seems to work just fine with latest Angular stable http://plnkr.co/edit/6oXDIF6P04FXZB335voR?p=preview maybe you are trying to use templateUrl thats pointing to somewhere it doesn't exist.
Another thing, use primitives only when strictly needed. In case you ever need to modify value of item, you won't be able to do so since you are using a primitive. Plus, if you need "more info" to go inside your isolated scopes, and to avoid attribute soup (attr-this="that", attr-that="boop", my-otherstuff="anotheritem.member", etc) you can pass the two-way bind of an object that handle more data.
Or if you need to share state through multiple controllers, directives, etc, use a service instead and use dependency injection, and there's no need to pass in objects/primitives to your isolated scope, and you can assure state, and that's best practice "the Angular way".
This fiddle works: http://jsfiddle.net/HB7LU/2844/ which is essentially the same thing just without the route information.
var myApp = angular.module('myApp',[]);
myApp.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
template: "<div>{{ item }}</div>"
};
});
function MyCtrl($scope) {
$scope.item = 'Superhero';
}
With the view:
<div ng-controller="MyCtrl">
<div example-directive item="item"></div>
</div>
This leads me to believe it could be a scope issue. So try encapsulating the controller scope variable in a container:
$scope.cont = { item: 'x' };
And in the view
<div example-directive item="cont.item"></div>

Using ng-repeat in directive with isolated scope

ng-repeat inside a directive with isolated scope is not picking up the property that's passed through '=' binding.
HTML:
<body ng-controller="myCtrl">
<div my-directive list="users">
<ul>
<li ng-repeat="item in list">
{{item.name}}
</li>
</ul>
</div>
</body>
JS:
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.users = [
{ name: 'John Doe'},
{ name: 'Jane Doe' },
{ name: 'Jesse Doe' }
];
});
app.directive('myDirective', [function() {
return {
restrict: 'A',
scope: {
list: '='
},
link: function(scope, element, attrs) {
}
}
}]);
Above code works fine with angular 1.0.8:
http://jsfiddle.net/shazmoh/4DN39/7/
but not with angular 1.2.14:
http://jsfiddle.net/shazmoh/4DN39/6/
What got changed with '1.2.x' that I'm missing?
From the migration to 1.2 guide:
Isolate scope only exposed to directives with scope property
Directives without isolate scope do not get the isolate scope from an
isolate directive on the same element. If your code depends on this
behavior (non-isolate directive needs to access state from within the
isolate scope), change the isolate directive to use scope locals to
pass these explicitly.
So, with > 1.2.0, isolated directives are completely isolated.

What makes these two Controllers related?

I'm reading a book on AngularJS and There's something that confuses me
There are two Controllers
EditCtrl
app.controller('EditCtrl', ['$scope', '$location', 'recipe',
function($scope, $location, recipe){
$scope.recipe = recipe;
$scope.save = function(){
$scope.recipe.$save(function(recipe){
$location.path('/view/', + recipe.id);
});
};
$scope.remove = function(){
delete $scope.recipe;
$location.path("/");
};
}]);
IngredientsCtrl
app.controller('IngredientsCtrl', ['$scope',
function($scope){
$scope.addIngredients = function(){
var ingredients = $scope.recipe.ingredients;
ingredients[ingredients.length] = {};
};
$scope.removeIngredient = function(index) {
$scope.recipe.ingredients.slice(index, 1);
};
}]);
What I don't understand is how the IngredientsCtrl is a child of EditCtrl. I can't see the relation. The book clearly states this case, and I'm sure it's the case because the example app works fine, but I need help understanding what it is that makes IngredientsCtrl a child of EditCtrl. Doesn't makes sense to me.
Edit: With relevent HTML
<div class="control-group">
<label class="control-label" for="ingredients">Ingredients:</label>
<div class="controls">
<ul id="ingredients" class="unstyled" ng-controller="IngredientsCtrl">
<li ng-repeat="ingredient in recipe.ingredients">
<input ng-model="ingredient.amount" class="input-mini">
<input ng-model="ingredient.amountUnits" class="input-small">
<input ng-model="ingredient.ingredientName">
<button type="button" class="btn btn-mini" ng-click="removeIngredient($index)"><i class="icon-minus-sign"></i> Delete </button>
</li>
<button type="button" class="btn btn-mini" ng-click="addIngredient()"><i class="icon-plus-sign"></i> Add </button>
</ul>
</div>
Edit: Snippet from book
All the other controllers that we saw so far are linked to particular views on the UI. But
the Ingredients Controller is special. It’s a child controller that is used on the edit pages
to encapsulate certain functionality that is not needed at the higher level. The interesting thing to note is that since it is a child controller, it inherits the scope from the parent
controller (the Edit/New controllers in this case). Thus, it has access to the
$scope.recipe from the parent.
Edit: with routing
var app = angular.module('guthub',
['guthub.directives', 'guthub.services']);
app.config(['$routeProvider',
function($routeProvider){
$routeProvider.
when('/', {
controller: 'ListCtrl',
resolve: {
recipes: function(MultipleRecipeLoader){
return MultipleRecipeLoader();
}
},
templateUrl: '/view/list.html'
}).
when('/edit/:recipeId', {
controller: 'EditCtrl',
resolve: {
recipe: function(RecipeLoader) {
return RecipeLoader();
}
},
templateUrl: '/view/recipeForm.html'
}).
when('/view/:recipeId', {
controller: 'ViewCtrl',
resolve: {
recipe: function(RecipeLoader) {
return RecipeLoader();
}
},
templateUrl: '/view/viewRecipe.html'
}).
when('/new', {
controller: 'NewCtrl',
templateUrl: '/view/recipeForm.html'
}).
otherwise({redirectTo: '/'});
}]);
Two controllers share a Parent-Child relationship if you place the ng-controller directive on two nested html elements.
If you take a look at your HTML template, you should see something like this:
<!-- parent controller -->
<div ng-controller="EditCtrl">
<!-- child controller -->
<div ng-controller="IngredientsCtrl"></div>
</div>
There is no such thing in angular as a child controller. However, you can place a controller inside of another in the dom.
<div ng-controller="EditCtrl">
<div ng-controller="IngredientsCtrl">
// Here you have access to the scope of both controllers
</div>
</div>
The answer to your question "What makes these two controllers related?" is "nothing". They can be nested as I described, but so could any two controllers be.
Both controllers in your example read from the scope. This is bad practice as stated by Miško Hevery himself (http://www.youtube.com/watch?v=ZhfUv0spHCY).
Paraphrasing:
Inside of a controller you shoul only do write-operations to the scope and in the templates you should do read only
Based on these code snippets. I would not recommend the book you read for learning angularjs.

Categories

Resources