Directive that creates child scope in AngularJS - angularjs

When creating complex forms I found the need of separating some parts of my view into different child scopes to be able to have individual visual properties.
The good example could be implementing 'click-to-edit' behaviour: when you have one html to view something and another to edit.
One of the solution is to create en directive that will have isolated scope. But in case if html markup for different properties differs a lot, you need to have kind of "double transclusion" (manually compile templates upon switching).
So more simplier is to have some small copy-pasting, but show dirrectly what is going on with view. This simplifies markup a lot.
Here is a sample code that illustrates that problem:
<span class="editable" >
<span ng-hide="editing">
{{user.first}} <span ng-click="editing = true"><i class="icon-pencil"></i></span>
</span>
<span ng-show="editing">
<input type="text" ng-model="user.first">
<span ng-click="editing = false"><i class="icon-ok"></i></span>
</span>
</span>
<span class="editable" >
<span ng-hide="editing">
{{user.last}} <span ng-click="editing = true"><i class="icon-pencil"></i></span>
</span>
<span ng-show="editing">
<input type="text" ng-model="user.last">
<span ng-click="editing = false"><i class="icon-ok"></i></span>
</span>
</span>
In this scenario 'child scopes' is first that come into mind.
But I didn't found directive that simply creates new scope in AngularJS. Is there a one?

As one of the very straight solution I've wrote an simple one-line directive:
.directive('childScope', function() {
return { scope: true, restrict:'AE' }
});
And use it just adding to <span class="editable" child-scope> in my source example.
But may be there is some standard directive for doing that?
If not, I consider this solution could be usefull for others.

Related

AngularJS ng-model not doing two way data binding

I have a label with an input box like so:
<label class="item item-input">
<input type="text" data-ng-model="description"
placeholder="Add a Description!">
</label>
As you can see this input box is binded to the scope "description"
In my controller I have something along the lines of this:
$scope.description = "";
$scope.submit = function() {
console.log($scope.description);
};
In my HTML I also have this line:
<div ng-show="description.length <= maxChars">{{description}}</div>
The submit function is called when the user hits the submit button after typing inside the input box. The description displays correctly. As the user types into the input box the description gets updated in the HTML but NOT in the controller.
I have a feeling it has something to do with setting description to an empty string but it is clearly getting updated as far as the HTML is concerned. But it keeps console.logging and empty string regardless.
In order to get this to work I had to pass description directly into the submit function (And also update the function accordingly):
<button class="button button-positive" data-ng-click="submit(description)">Submit</button>
This is completely unnecessary since Angular should be doing Two-way data binding automatically but it's not.
Anyone have any ideas?
Any help would be appreciated.
EDIT:
Here is the full HTML due to popular demand.
<ion-view title="Moments" id="page2">
<ion-content padding="true" class="has-header">
<img src="{{picture}}" height="300px" width="100%"> </img>
<div class="row">
<div ng-show="description.length > maxChars" style= "color: red">{{description}}</div>
<div ng-show="description.length <= maxChars">{{description}}</div>
</div>
<div class="row" ng-if="description">
<div ng-show="description.length > maxChars" style= "color: red">{{description.length}} / {{maxChars}}</div>
<div ng-show="description.length <= maxChars" style= "color: green">{{description.length}} / {{maxChars}}</div>
</div>
<div class="row">
<div class="col">
<label class="item item-input">
<input type="text" data-ng-model="description" placeholder="Add a Description!">
</label>
</div>
<button style="margin-right: 5px" class="col col-10 button button-positive" data-ng-click="checkDescription(description)">OK</button>
</div>
<div class="row">
<div class="col"><ion-checkbox data-ng-change="changeLocation()" data-ng-model="location">Show Location</ion-checkbox></div>
</div>
<div class="row">
<div class="button-bar">
<button class="button button-assertive" data-ng-click="cancel()">Cancel</button>
<button class="button button-positive" data-ng-click="submit(description)">Submit</button>
</div>
</div>
</ion-content>
</ion-view>
I am also aware that in this question the submit function takes no parameters. That's how I would like it to be. Currently my submit button takes one parameter(the description). This should not be nessesary. I am having the same problem with the function 'checkDescription' as well. That function should also have no parameters but again, I am forced to pass the description directly into the function. Something I prefer not to do.
Executive Summary:
In AngularJS, a child scope normally prototypically inherits from its parent scope. One exception to this rule is a directive that uses scope: { ... } -- this creates an "isolate" scope that does not prototypically inherit.(and directive with transclusion) This construct is often used when creating a "reusable component" directive. In directives, the parent scope is used directly by default, which means that whatever you change in your directive that comes from the parent scope will also change in the parent scope. If you set scope:true (instead of scope: { ... }), then prototypical inheritance will be used for that directive.
Scope inheritance is normally straightforward, and you often don't even need to know it is happening... until you try 2-way data binding (i.e., form elements, ng-model) to a primitive (e.g., number, string, boolean) defined on the parent scope from inside the child scope. It doesn't work the way most people expect it should work. What happens is that the child scope gets its own property that hides/shadows the parent property of the same name. This is not something AngularJS is doing – this is how JavaScript prototypal inheritance works. New AngularJS developers often do not realize that ng-repeat, ng-switch, ng-view, ng-include and ng-if all create new child scopes, so the problem often shows up when these directives are involved. (See this example for a quick illustration of the problem.)
This issue with primitives can be easily avoided by following the "best practice" of always have a dot '.' in your ng-models – watch 3 minutes worth. Misko demonstrates the primitive binding issue with ng-switch.
--AngularJS Wiki -- The Nuances of Scope Prototypal Inheritance
I tried to do the '.' notation in my scope but whenever I try to reference it like this:
$scope.moment.description = "";
It says "Cannot read property 'description' of undefined.
The code needs to create the object before assigning a value to a property:
$scope.moment = {};
$scope.moment.description = "";
//OR
$scope.moment = { description: "" };

ng-click showing all the hidden text field on ng-repeat rather than one in Angular

I started to work with Angular, it's pretty good to implement, I stuck with a single issue at ng-click
I am getting data dynamically and showing with ng-repeat, and I want to update the data at pencil click and for it I am using input text element, but when I click on pencil It's opening all the text fields
Here is my HTML code
<
div ng-repeat="item in scroller.items track by $index">
<div class="secHead text-center">
<button class="common btnDarkGrey" data-ng-hide="hideCatButton">{{item.category_name}}</button>
<input type="text" id="focus-{{$index}}" class="common btnDarkGrey editDashboardCategory" name="editCategory" value="" data-ng-model="item.category_name" data-ng-show="hideField">
<span data-ng-click="updateCategory(item.category_id,item.category_name,$index)" class="chkOneDone" data-ng-show="hideOkButton">Done</span>
<div class="pull-right">
</div>
</div>
</div>
And here I Angular code
$scope.updateCategory=function(category_id,updated_cat_name, $index){
Category.updateCat($rootScope,$scope,$index,$http,$timeout,updated_cat_name,old_cat_name,category_id);
};
$scope.updatePen=function($index){
old_cat_name=$scope.scroller.items[$index].category_name
$scope.hideField=true;
$rootScope.hideOkButton=true;
$rootScope.hideCatButton=true;
};
I created a Category service to perform task like update
I didn't get any proper solution yet.
Can anybody help me?
Thank You.
If you only want to hide/show one of the elements in the list you need to specify that in some fashion. Right now you have a three rootScope booleans:
$scope.hideField=true;
$rootScope.hideOkButton=true;
$rootScope.hideCatButton=true;
being set for the entire list, and you need to set a show properties on each individual in the list.
In your controller function you can do something like this before you expect a click:
//normal for loop so that you have the index
for(var i=0; i < $scope.scroller.items.length; i++){
$scope.scroller.items[i].show = false;
}
Then you can do something like this to actually show the fields:
HTML:
div ng-repeat="item in scroller.items track by $index">
<div class="secHead text-center">
<button class="common btnDarkGrey" ng-hide="!item.show">
{{item.category_name}}</button>
<input type="text" id="focus-{{$index}}" class="common btnDarkGrey editDashboardCategory" name="editCategory" value="" ng-model="item.category_name" ng-hide="!item.show">
<span data-ng-click="updateCategory(item.category_id,item.category_name,$index)" class="chkOneDone" ng-show="item.show">Done</span>
<div class="pull-right">
</div>
</div>
</div>
Controller:
//controller declaration --
$scope.updatePen = function(index){
$scope.scroller.items[index].show = true;
};
It's my understanding that you need all three properties to show once a click happens, so I condensed all the show properties into one single show property.
Your view only sees that hideField is true and performs that action for all of the items in your array. I hope this helps!

How to ng-include at click on link

I'm new on AngularJS and I need to include another template by clicking on a link.
I have a nav.html and a header.html. Both included in the index.html.
In header.html I have
<li class="search-box visible-md visible-lg" data-ng-include=" 'views/calls/search.html' ">
calls/search.html
<div class="input-group" data-ng-controller="callSearchCtrl">
<span class="input-group-addon"><i class="fa fa-search text-muted"></i></span>
<input type="text" class="form-control" placeholder="Suchen..."></div>
And I have to include another template in the header by clicking on a menu point (i.e. Contacts) to load the contacts/search.html
<div class="input-group" data-ng-controller="contactsSearchCtrl">
<span class="input-group-addon"><i class="fa fa-search text-muted"></i></span>
<input type="text" class="form-control" placeholder="Suchen..."></div>
to get another search controller.
The case is, that I have a search bar in the header, where I want to search in the loaded content template.
Maybe I've got the wrong mindset to solve this...
Anyone knows a solution?
ADDITION:
Now I put different ng-clicks in my nav like:
<i class="fa fa-users"></i><span>Kontakte</span>
But do I have to put the scope function in my HeaderCtrl or in my NavCtrl?
P.S. Sorry for my bad english :-)
Cheers
bambamboole
The simplest and probably most idiomatic is as #coder-john suggests.
data-ng-include="search.option"
In your controller,
$scope.search = {};
$scope.selectType = function (type) {
$scope.search.option = 'views/'+type+'/search.html';
};
$scope.selectType('calls');
where your menu options should invoke the proper handlers, such as
data-ng-click="selectType('calls')"
or
data-ng-click="selectType('contacts')"
as appropriate.

How to do inline editing in angularjs without Template?

am trying to write an inline editing function without using a template as outlined here
http://plnkr.co/edit/EsW7mV?p=preview
You can just place the code of the template in the main page.
<li ng-repeat="todo in todos" inline-edit="todo.title" on-save="updateTodo(todo.title)" on-cancel="cancelEdit(todo.title)">
<div>
<input type="text" on-enter="save()" on-esc="cancel()" ng-model="model" ng-show="editMode">
<button ng-click="cancel()" ng-show="editMode">cancel</button>
<button ng-click="save()" ng-show="editMode">save</button>
<span ng-mouseenter="showEdit = true" ng-mouseleave="showEdit = false">
<span ng-hide="editMode" ng-click="edit()">{{model}}</span>
<a ng-show="showEdit" ng-click="edit()">edit</a>
</span>
</div>
</li>
Here there is a fiddle:
http://jsfiddle.net/siliconball/QwDn9/2/
Also temeber to take away the templateUrl: 'inline-edit.html'
If you need the controller scope for any reason place scope: false in the directive. But then you will have to track which option are you editing in any moment (maybe using the id). If that is your situation i suggest to refactor a bit, as you may know, probably is not the best option.
If your situation, i guess it is, is that you want to write it all in one page because you are generating it through some CGI or dynamic content script and you don't want to write the same code in different pages (+scripts interfaces ...), then i suggest also to move the inline-edit="todo.title" and all the directive stuff in the <div> for the sake of orthogonality.

CRUD detail screen, conditional new or edit

I'm working on a CRUD detail screen in Angular and wish to reuse a single template. Here's the initial template psuedo-code, the crude beginnings of an edit screen...
<h1>{{fixtureType.Label}}</h1>
<form>
<span>Fixture Type Details</span>
<label>Type</label>
<input>{{fixtureType.Type}}</input>
<label>Watts</label>
<input>{{fixtureType.Watts}}</input>
<label>Cost</label>
<input>{{fixtureType.Cost}}</input>
</form>
Suppose I want to conditionally use the same template as a new screen as well, that would look something like this
<h1>New Fixture Type</h1>
<form>
<span>Fixture Type Details</span>
<label>Type</label>
<input/>
<label>Watts</label>
<input/>
<label>Cost</label>
<input/>
</form>
If this were straight Javascript, a simple condition like bIsEdit = fixtureType != null would do the trick. From what I've read so far there is no conditional or way to drop in a chunk of JS into an Angular view.., or is this where I reach for a custom directive or filter?
Now I could have 2 views and handle the routing appropriately, but would prefer to have a single one to avoid code duplication.
So what is the Angular way to handle something like this?
I would prefer separate routes for each. To keep the edit and new HTML together, you could use ng-switch with essentially two templates, but consider putting them both into one partial, so you can ng-include it in the two different views:
<span ng-switch on="mode">
<span ng-switch-when="edit">
<h1>{{fixtureType.Label}}</h1>
<form>
<span>Fixture Type Details</span>
<label>Type</label>
<input ng-model='fixtureType.Type' ...>
...
</span>
<span ng-switch-default>
<h1>New Fixture Type</h1>
<form>
<span>Fixture Type Details</span>
<label>Type</label>
<input ng-model="fixtureType.Type" ...>
...
</span>
</span>
I use following approach to minimize form duplication when differences between new and editable versions aren't too complex:
<form ng-submit="mySubmitMethod()">
<!-- fields models bound to "activeItem"-->
<button >
<span ng-show="editMode>Update</span>
<span ng-show="!editMode">Create</span>
</button>
</form>
$scope.activeItem={};
$scope.editMode=false;
$scope.mySubmitMethod=function(){
if($scope.editMode){
/* do update of existing*/
}else{
/* process new form*/
}
$scope.resetform()
});
$scope.EditUser=function(user){
$scope.editMode=true;
$scope.activeItem=angular.copy( user);
})
$scope.newUser=function(){
$scope.editMode=false;
$scope.activeItem={};
})

Resources