supplying AngularJS controller data - javascript

Assuming a controller is there to manipulate some data on the scope, what is the best practice to supply the controller with that data?
For example, if I have a list of items I might want a ListController to manipulate the list, and an ItemController to manipulate an individual item. But how do I give each ItemController an item to use?
In the case below, how would doSomethingToItem access the item. When nested in the ngRepeat the item is $scope.item, but in the selection view the item we want to manipulate is $scope.selection.
angular.module('MyApp', [])
.controller('ListController', function($scope, $http) {
$scope.list = $http.get(...);
$scope.selection = null;
})
.controller('ItemController', function($scope) {
$scope.doSomethingToItem = function() {
...
};
})
<div ng-controller="ListController">
<div ng-repeat="item in list" ng-click="selection = item">
<div ng-controller="ItemController">
<button ng-click="doSomethingToItem()">Do Something</button>
</div>
</div>
<div ng-show="selection"
<div ng-controller="ItemController">
<button ng-click="doSomethingToItem()">Do Something</button>
</div>
</div>
</div>
Isn't this a common structure, or is my thinking backwards?

You should understand that Angular would create n+1 ItemController in your case. N for items and 1 for the selection section.
To make passing the object that needs to be worked on easier you can change the method signature to
doSomethingToItem(item) and in html do
<button ng-click="doSomethingToItem(item)">Do Something</button> at both places.
Or else for the repeat case the item variable contains your object that you can access in ItemController
selection variable contains the reference to the select controller, which can be reference from the instance of the controller defined under selection section.
Update: The expression in ng-repeat and selection would differ
<button ng-click="doSomethingToItem(item)">Do Something</button>
and
<div ng-show="selection"
<div ng-controller="ItemController">
<button ng-click="doSomethingToItem(selection)">Do Something</button>
</div>
</div>

You could pass the item data model like this:
<div ng-init="instance = item" ng-controller="ItemController">
</div>
"instance" will be a reference to list array data model item in "ListController".
And you could access its property in your ItemController function closure:
.controller("ItemController", function($scope){
$scope.instance={};
$scope.doSomething = function(){
console.log($scope.instance.name);
}
$scope.$watch('instance',function(){
console.log("iitem changed");
},true);
});
I'm not quite sure what feature do you want to achieve in your "selection" implementation.
I think you want to implement a selected list and list item will be added to it when user clicked the list item. You could try to create a "selected list" model to control the selected list view if you want to add the selected item to a list.
ListController.js
.controller("ListController", function($scope){
$scope.selectedList = [];
$scope.addItem = function(item){
$scope.selectedList.push(item);
}
});
HTML
<div ng-repeat="selected in selectedList">
<div ng-init="instance = selected" ng-controller="ItemController">
<span>{{instance.name}}</span>
<button ng-click="doSomething()">selectedAction</button>
</div>
</div>
I wrote a simple multi-selected list example as following:
HTML
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<title>Nested controller</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.6/angular.min.js"></script>
<script src="js/nestedController.js"></script>
</head>
<body>
<div ng-controller="parentCtrl">
<h2>List</h2>
<div ng-repeat="item in list">
<input type="checkbox" ng-model="item.selected" ng-checked="item.selected"/>
<div ng-init="instance = item" ng-controller="childCtrl">
<span>{{instance.name}}</span>
<button ng-click="doSomething()">doSomething</button>
</div>
</div>
<h2>Selected</h2>
<div ng-repeat="selected in selectedList">
<div ng-init="instance = selected" ng-controller="childCtrl">
<span>{{instance.name}}</span>
<button ng-click="selectedAction()">selectedAction</button>
</div>
</div>
</div>
JS
angular.module("myApp",[])
.controller("parentCtrl",function($scope){
//test data
$scope.list = [{name:'item1',age:'12',selected:false},{name:'item2',age:'18',selected:false}];
//use model to control selected list view
$scope.selectedList = [];
//refresh the selected list model when the list checked stauts has been updated
$scope.$watch('list',function(){
console.log("parent controller detected change");
$scope.selectedList = [];
$scope.list.forEach(function(elem,index,array){
if(elem.selected===true){
$scope.selectedList.push(elem);
}
});
},true);
})
.controller("childCtrl",function($scope){
$scope.instance={}
$scope.doSomething = function(){
alert("I'm the item: "+$scope.instance.name);
}
$scope.selectedAction = function(){
alert("I'm the selected item: "+$scope.instance.name);
}
//could register a watcher to monitor the model status
$scope.$watch('instance',function(){
console.log("child controller detected change");
},true);
});
Here is the jsFiddle demo
Hope this is helpful.

Related

Need to add multiple divs with same content inside a single div on a click against the parent div

Here is my piece of code, a function to add a meal template:
vm.addMealTemplate = function() {
$scope.mealCount++;
$compile( $(document.createElement('div')).attr("id", 'mealDiv' + $scope.mealCount).addClass("mealDiv"+$scope.mealCount).after().html(
'<select ng-options="(option.name ) for option in mealOptions" ng-model="selectedOption'+ $scope.mealCount+'" />' +
'<input type="text" placeholder="Meal timings" id="time'+ $scope.mealCount +'"/>' +
'<a id="mealCount" ng-class="mealCount" ng-click="addItemCategory()" uib-tooltip="Add category" tooltip-trigger="mouseenter" tooltip-placement="bottom"><i class="fa fa-plus"></i></a>'
).appendTo("#meals")
// $("#meals").append(newMealDiv)
)($scope);
}
On clicking calling the addItemCategory() for the specific div, I want another div to get added as a child of that div. There can be mutiple meal templates, and for each template I can call the addItemCategory mutliple times, and I want the category to be added to the same div for which the function has been called. How do I achieve this?
Currently I am using mealCount variable from scope to have the context, but once it gets increased, I have no way to access the divs added previously, to add the new element to that div. Any way using jQuery or AngularJs?
You can use ng-repeat
For example:
angular.module('app', []).
controller('ctrl', function($scope) {
$scope.meals = [];
});
.meal {
border:1px solid;
padding:10px;
margin-bottom:10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div ng-repeat="meal in meals" class="meal">
<select ng-model="meal.count">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<input type="text" placeholder="Meal timings" ng-model="meal.timing" />
<div>
<div>Categories:</div>
<div ng-repeat="cat in meal.categories track by $index">
<input type="text" ng-model="meal.categories[$index]" />
</div>
<button ng-click="meal.categories.push('')">Add Category</button>
</div>
</div>
<button ng-click="meals.push({categories:[]})">Add meal</button>
<hr />
{{meals | json}}
</div>
Note: I changed the models etc. it's just example..
Here's an example Plunker on how your problem can be solved with ng-repeat in Angular:
http://plnkr.co/edit/POt7nFc4GqFU67SWdMp7?p=preview
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="https://code.angularjs.org/1.5.5/angular.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="restaurant" ng-controller="meal-controller">
<div ng-repeat="meal in meals track by $index">
<select ng-options="option.name for option in mealOptions" ng-model="meal.selectedMeal"></select>
<input type="text" placeholder="Meal timings" id="{{'time'+ $index }}" ng-model="meal.timing"/>
<a id="mealCount" ng-class="mealCount" ng-click="addItemCategory()" uib-tooltip="Add category" tooltip-trigger="mouseenter" tooltip-placement="bottom"><i class="fa fa-plus"></i></a>
</div>
<button ng-click="addMeal()">Add meal</button>
</body>
</html>
-
// Script.js
angular.module('restaurant', [])
.run( ['$rootScope', function( $rootScope ) {
}]
);
angular.module('restaurant').controller('meal-controller', [ '$scope', function( $scope ) {
$scope.meals = [];
$scope.mealOptions = [{ name: "option1"}, { name: "option2"}, { name: "option3" } ];
$scope.addMeal = function() {
$scope.meals.push({ selectedMeal: null, timing: "timing" });
}
$scope.addItemCategory = function() {
}
}]);
I am not entirely sure about how you want the logic to work, but I am sure that you can modify this example to fit your needs.
Ng-repeat works like a loop which prints the content it is wrapping until the end of the array. Ng-repeat has a lot of features like tracking elements in the array by index, this can used to (like in the example) give each input an unique id.
If you need to make some specific changes to a meal, you can pass it as an argument, for example if you want to delete a specific meal you can have a button inside the ng-repeat like this:
<button ng-click="deleteMeal(meal)">Delete Meal</button>
This means that you do not have to access the meal specifically by, for example, its id with jQuery.
I would say it is not recommended to mix angular and jQuery the way you are doing now. Try to stick with Angular and avoid jQuery. In some special cases where you need to do element specific modifications it can be achieved by using jQlite:
https://docs.angularjs.org/api/ng/function/angular.element

Adding objects to the DOM after adding new data to the list

I have an array of objects which i populate on a button click.
When populating this array i make sure that i only add 10 objects to it.
When this is all loaded in the dom i give the user the oppertunity to add a few more objects.
I do this like this:
$scope.Information = [];
$.each(data, function (i, v) {
if (i<= 9)
$scope.Information.push(data[i]);
if(i >= 10) {
cookieList.push(data[i]);
}
}
if (cookieList.length) {
localStorage.setItem("toDoList", JSON.stringify(cookieList));
$(".showMore").removeClass("hidden");
}
$(".showMore").on("click", function() {
var obj = JSON.parse(localStorage.getItem("toDoList"));
console.log(obj);
console.log(obj.length);
SetSpinner('show');
$scope.Information.push(obj);
SetSpinner('hide');
//$.removeCookie("toDoList2");
});
part of the HTML:
<div ng-repeat="info in Information" class="apartment container" style="padding-right:35px !important">
<div class="row" style="height:100%">
<div class="col-md-1 col-xs-12">
<div>
<h4 class="toDoListHeadings">Nummer</h4>
<div style="margin-top: -15px; width:100%">
<span class="toDoListItems number">
{{info.orderraderid}}
</span>
</div>
</div>
</div>
</div>
</div>
My issue: When i add objects to my array of objects "$scope.Information.push(obj);" I assumed that they would get added in the DOM but they do not, how do i do this the angular way?
EDIT MY SOLOUTION:
edited the HTML to use ng-click and the method is as follows:
$scope.addMore = function() {
var obj = JSON.parse(localStorage.getItem("toDoList"));
SetSpinner('show');
$.each(obj, function(i,v) {
$scope.Information.push(v);
});
SetSpinner('hide');
}
Here is the angular way:
 The view
<!-- Reference your `myapp` module -->
<body data-ng-app="myapp">
<!-- Reference `InfoController` to control this DOM element and its children -->
<section data-ng-controller="InfoController">
<!-- Use `ng-click` directive to call the `$scope.showMore` method binded from the controller -->
<!-- Use `ng-show` directive to show the button if `$scope.showMoreButton` is true, else hide it -->
<button data-ng-click="showMore()" data-ng-show="showMoreButton">
Show more
</button>
<div ng-repeat="info in Information" class="apartment container" style="padding-right:35px !important">
<div class="row" style="height:100%">
<div class="col-md-1 col-xs-12">
<div>
<h4 class="toDoListHeadings">Nummer</h4>
<div style="margin-top: -15px; width:100%">
<span class="toDoListItems number">
{{info.orderraderid}}
</span>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
The module and controller
// defining angular application main module
var app = angular.module('myapp',[])
// defining a controller in this module
// injecting $scope service to the controller for data binding with the html view
// (in the DOM element surrounded by ng-controller directive)
app.controller('InfoController',function($scope){
$scope.Information = [];
$scope.showMoreButton = false;
// Bind controller method to the $scope instead of $(".showMore").on("click", function() {});
$scope.showMore = function(){
var obj = JSON.parse(localStorage.getItem("toDoList"));
console.log(obj);
console.log(obj.length);
SetSpinner('show');
$scope.Information.push(obj);
SetSpinner('hide');
//$.removeCookie("toDoList2");
};
$.each(data, function (i, v) {
if (i<= 9) $scope.Information.push(data[i]);
if(i >= 10) cookieList.push(data[i]);
});
if (cookieList.length) {
localStorage.setItem("toDoList", JSON.stringify(cookieList));
//$(".showMore").removeClass("hidden");
$scope.showMoreButton = true; // use $scope vars and ng-class directive instead of $(".xyz").blahBlah()
}
});
You should not use JQuery, use ng-click to detect the click, because angular has no idea when JQuery is done and when it needs to refresh the interface

Nested Angular Controllers and Views management

I guess it is best to describe it with a picture. I have an angular app and here is a simple view.
Obvious explanation: list shows all the entities, if you click on an entity you can edit it in the form that is hidden by default and similar action applies to adding a new entity.
the issue
I know it is basic example so here the solution might be an overkill but I want to separate the logic of 'Add new entity', 'Edit entity' and 'Entities list'. I thought I could implement it like this:
<div ng-include="'userAddForm.html'"
ng-show="???"
ng-controller="AddUser as add">
</div>
<div ng-include="'userEditForm.html'"
ng-show="???"
ng-controller="AddEdit as edit">
</div>
<div class="panel panel-default">
... list managed by the current controller
</div>
What I miss
I have a difficulty in sharing a state of the hidden parts. For example some boolean flag. For instance:
Click on the entity shows the edit form
Save/Cancel in the edit form hides the part
Then, I think the first step is the responsibility of list-controller, but save/cancel part goes to edit-controller. It would be only possible to share the value with a service included in both - but that does not seem reasonable either.
I think there is some simple solution I can not see and I am open for any advice. Thanks!
If your goal is a simple solution with just a boolean being toggled in the model, you can use child controllers like this:
http://plnkr.co/edit/P1ncToJwqvxt9F9MTF5E?p=preview
The child controllers will inherit the scope of the parent controller and can directly edit the values. I have the edit child controller filtering for editMode==true, so when the parent changes that value, the child controller automatically shows the item. All changes are updated live and the child controller simply toggles the editMode property to remove it from the editing area.
Similar logic is used for the add child controller.
The views look like this:
index.html
<div ng-controller="myCtrl">
<div ng-controller="addCtrl" ng-include="'userAddForm.html'">
</div>
<div ng-controller="editCtrl" ng-include="'userEditForm.html'">
</div>
<h1>Listing</h1>
<ul>
<li ng-repeat="item in items | filter:{addMode:false}">
{{item.id}}
{{item.name}}
<button ng-click="startEditing(item)">[ edit ]</button>
</li>
</ul>
<button ng-click="startAdding()">[ add ]</button>
<div>Debug:<br>{{items}}</div>
</div>
userAddForm.html
<ul>
<li ng-repeat="item in items | filter:{addMode:true}">
<input type="text" ng-model="item.id">
<input type="text" ng-model="item.name">
<button ng-click="add(item)">[ add ]</button>
<button ng-click="cancel(item)">[ cancel ]</button>
</li>
</ul>
userEditForm.html
<ul>
<li ng-repeat="item in items | filter:{editMode:true}">
<input type="text" ng-model="item.id">
<input type="text" ng-model="item.name">
<button ng-click="save(item)">[ save ]</button>
</li>
</ul>
And the controllers look like this:
angular.module('myApp.controllers',[])
.controller('addCtrl', function($scope) {
$scope.add = function(item) {
item.addMode = false;
}
$scope.cancel = function(item) {
$scope.items.pop(item);
}
})
.controller('editCtrl', function($scope) {
$scope.save = function(item) {
item.editMode = false;
}
})
.controller('myCtrl', function($scope) {
$scope.items = [
{name:'aap', id:"1", editMode:false, addMode:false},
{name:'noot', id:"2", editMode:false, addMode:false},
{name:'mies', id:"3", editMode:false, addMode:false},
{name:'zus', id:"4", editMode:false, addMode:false}
];
$scope.startAdding = function(){
$scope.items.push({addMode:true});
};
$scope.startEditing = function(item){
item.editMode = true;
};
});
You can achieve this using Angular state routing.In which you will create state (different views) like -
header
addEntity
editEntity
listEntity
refer https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views
Sharing state can be implemented by creating a service which is than injected to all interested párties (controllers), service can hold data which controllers can be bound to and display in template. Services in Angular JS are singletons so all the controllers are accesing and mutating shared state.

AngularJS Dynamic Template with indexed scope variable arrays

I'm using AngularJS and trying to create a form where I can dynamically add new inputs, similar to this fiddle: http://jsfiddle.net/V4BqE/ (Main HTML below, working code in fiddle).
<div ng-app="myApp" ng-controller="MyCtrl">
<div add-input>
<button>add input</button>
</div>
</div>
I would like to be able to use a HTML template for my form since the input I'm adding is ~300 lines long. My issue is I cannot figure out how to index the scope variable containing the data when used in a template. I've tried to make my own modified version of the above code on plnkr http://plnkr.co/edit/4zeaFoDeX0sGTuBMCQP2?p=info . However, when I click the button no form elements appear.
Online (plnkr) I get a 404 not found for my template.html, but I think that is just a plnkr limitation. On my machine with a Python HttpServer I get an Error: [$parse:syntax] for the $templateRequest and a TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'. when using the $http.get method.
Any advice for getting the indexed scope variable array to work with an external html file?
Thanks, JR
Edit: Update plnkr link
You can do it without directive & external template
what you are trying to do does not require a directive (it actually much simple with the basic angularjs tools)
http://plnkr.co/edit/LVzGN8D2WSL2nR1W7vJB?p=preview
html
<body>
<div class="container" ng-app="myApp" ng-controller="MyCtrl">
<button class="btn btn-primary" type="button" ng-click="addPhoneInput()">add input</button>
<form>
<div ng-repeat="item in telephoneNumbers">
<hr>
<input type="text" ng-model="item.phone">
</div>
</form>
<hr>
<div class="well">
<h4>Phone Numbers,</h4>
<p ng-repeat="item in telephoneNumbers">{{item.phone}}</p>
</div>
</div>
</body>
js
var app = angular.module('myApp', []);
app.controller('MyCtrl', ['$scope', function($scope) {
// Define $scope.telephone as an array
$scope.telephoneNumbers = [];
$scope.addPhoneInput = function() {
$scope.telephoneNumbers.push({});
};
// This is just so you can see the array values changing and working! Check your console as you're typing in the inputs :)
$scope.$watch('telephoneNumbers', function(value) {
console.log(value);
}, true);
}]);
If you insist using a directive,
http://plnkr.co/edit/BGLqqTez2k9lUO0HZ5g1?p=preview
phone-number.template.html
<div>
<hr>
<input type="text" ng-model="ngModel" >
</div>
html
<body>
<div class="container" ng-app="myApp" ng-controller="MyCtrl">
<button class="btn btn-primary" type="button" ng-click="addPhoneInput()">add input</button>
<form>
<phone-number ng-repeat="item in telephoneNumbers" ng-model="item.phone"></phone-number>
</form>
<hr>
<div class="well">
<h4>Phone Numbers,</h4>
<p ng-repeat="item in telephoneNumbers">{{item.phone}}</p>
</div>
</div>
</body>
js
var app = angular.module('myApp', []);
app.controller('MyCtrl', ['$scope', function($scope) {
// Define $scope.telephone as an array
$scope.telephoneNumbers = [];
$scope.addPhoneInput = function() {
$scope.telephoneNumbers.push({});
};
// This is just so you can see the array values changing and working! Check your console as you're typing in the inputs :)
$scope.$watch('telephoneNumbers', function(value) {
console.log(value);
}, true);
}]);
app.directive('phoneNumber', function(){
return {
replace:true,
scope: {
ngModel: '=',
},
templateUrl: "phone-number.template.html"
}
});

Why does my View not update in Angularjs on click, though I am able to get the content?

<div class="test" ng-controller="Ctrl">
<div ng-repeat="task in tasks">
<button ng-click="removeTask(task.id);">remove</button>
<div class="content">{{taskId}}</div>
</div>
<div>
var app = angular.module('app', []);
function Ctrl($scope) {
$scope.tasks = [{id:1,'name':'test1'}, {id:2,'name':'test2'}, {id:3,'name':'test3'}];
$scope.removeTask = function(taskId){
alert("Task Id is "+taskId);
};
}
The content I get in alert needs to be put in div, but the div won't get updated, what am I not doing correctly?
jsFiddle Demo
If you want id of task - task.id. taskId is just name for function parameter, it's undefined outside this function.
<div class="content">{{task.id}}</div>
But I suppose, best practice would be to pass whole object to click function:
$scope.removeTask = function(task){
alert("Task Id is " + task.id);
};
http://jsfiddle.net/PSz7t/3/
There different way's how you can achieve what are you looking for.
Here is mine. Since you need write an 'alert' for each removed item you need to save the status for each item so you can decide to show the alert or not
<div class="test" ng-controller="Ctrl">
<div ng-repeat="task in tasks">
<button ng-click="removeTask(task);">remove</button>
<div class="content"> <span ng-show="task.status=='deleted'">Task Id is {{task.id}} </span> </div>
</div>
<div>
http://jsfiddle.net/PSz7t/9/
var app = angular.module('app', []);
function Ctrl($scope) {
$scope.tasks = [{id:1,'name':'test1',status:'active'}, {id:2,'name':'test2',status:'active'}, {id:3,'name':'test3',status:'active'}];
$scope.removeTask = function(task){
task.status='deleted';
};
}

Categories

Resources