I'm getting started with Angular.js and I'm wondering how to do something along the lines of this (pseudocode):
<li ng-repeat="item in items">
<# if(item.dataType == "string") { #>
<input type="text" />
<# } else if(...) { #>
<input type="password" />
<# } #>
</li>
I know the above code is not angularish, and I know that for simple processing I could use a conditional ng-hide or ng-show or something similar. But for complex behavior, if I had to perform various data checks and business logic, how could I dynamically generate DOM elements with Angular.js?
Within the angular world, DOM manipulation is accomplished using angularjs directives. Here is the angular documentation on directives: https://docs.angularjs.org/guide/directive, you would do well to read through this.
Here is some sample code that will accomplish the idea of your psuedo code:
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function ($scope){
$scope.items = [
42, "hello, world!", 3.14, "i'm alive!"
]
});
myApp.directive('myInputDirective', function () {
return {
restrict: 'E',
replace: true,
template: '<div></div>',
link: function (scope, element, attrs) {
if (typeof scope.current === "string") {
element.append('<input type="text">');
} else {
element.append('<input type="password">');
}
}
}
});
and here's how the html would look:
<div ng-controller="MyController">
<ul ng-repeat="item in items" ng-init="current = item">
<my-input-directive></my-input-directive>
</ul>
</div>
Here is a plnkr with the working example: http://plnkr.co/edit/iiS4G2Bsfwjsl6ThNrnS?p=preview
Directives are how the DOM is manipulated in angular. First thing to notice is that angular has a set of directives that come out of the box, we're using a few above (ng-repeat, ng-init, ng-controller). Above we've created a custom directive that will analyze the data type of each item in the items array of our MyController controller, and append the correct html element.
I imagine that you already understand the ng-repeat directive, so I'll skip that. I'll explain what I'm doing with the ng-init directive though. The ng-init directive allows you to evaluate an expression in the current scope. What this means is that we can write an expression that is evaluated in our current controllers scope, in this case the MyController scope. I am using this directive to create an alias for our current item named current. We can use this inside our directive to check what type the current item in the array iteration is.
Our directive myInputDirective, is returning an object with a few different properties. I won't explain them all here (I'll let you read the documentation), but I will explain what the link function is and what I am doing with it. A link function is typically how we modify the DOM. The link function takes in the current scope (in this case the scope of MyController), a jqLite wrapped element that is associated with the directive, and the attrs which is a hash object with key-value pairs of normalized attribute names and values. In our case, the important parameters are the scope, which contains our current variable, and the element, which we will append the correct input onto. In our link function, we're checking the typeof our current item from our items array, then we are appending an element onto our root element based on what the type of the current item is.
For this particular problem, what I'm doing above is overkill. But based off of your question I figured you were looking for a starting point for more advanced uses of angular apart from the built in directives that angular provides. These are somewhat advanced topics in angular, so I hope that what I've said make some sense. Check out the plunker and play around with it a bit, and go through some of the tutorials on https://docs.angularjs.org/guide. Hope this helps!
You can use ng-show to conditionally hide and show elements e.g.:
<input ng-show="item.dataType === 'string'" type="text"/>
<input ng-show="..." type="password"/>
Assuming your object looks like this:
$scope.items = [
{
dataType: 'string',
value: 'André Pena'
},
{
dataType: 'password',
value: '1234'
},
{
dataType: 'check',
value: true
}
];
Option #1 - ng-switch plunker
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
<div ng-switch="item.dataType">
<div ng-switch-when="string" ><input type="text" ng-model="item.value" /></div>
<div ng-switch-when="password" ><input type="password" ng-model="item.value" /></div>
<div ng-switch-when="check" ><input type="checkbox" ng-model="item.value" /></div>
</div>
</li>
</ul>
</body>
Option #2 - ng-show plunker
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
<div ng-show="item.dataType == 'string'" ><input type="text" ng-model="item.value" /></div>
<div ng-show="item.dataType == 'password'" ><input type="password" ng-model="item.value" /></div>
<div ng-show="item.dataType == 'check'" ><input type="checkbox" ng-model="item.value" /></div>
</li>
</ul>
</body>
Option #3 - ng-hide plunker
<body ng-controller="MainCtrl">
<ul>
<li ng-repeat="item in items">
<div ng-hide="!(item.dataType == 'string')" ><input type="text" ng-model="item.value" /></div>
<div ng-hide="!(item.dataType == 'password')" ><input type="password" ng-model="item.value" /></div>
<div ng-hide="!(item.dataType == 'check')" ><input type="checkbox" ng-model="item.value" /></div>
</li>
</ul>
</body>
You should use the ng-if directive.
<input ng-if="item.dataType === 'string'" type="text"/>
<input ng-if="..." type="password"/>
The problem with using ng-show like #rob suggested, is that it only uses CSS to hide the element, which is not ideal if you want the two inputs to have the same name/ID.
ng-if will remove the element from the DOM if the condition is not true.
for a problem this simple there's no need to go and implement your own directive.
Related
I know that if I use the directive ng-repeat, like below, I get every element inside and including the div to repeat on the DOM.
<div class="col col-3" ng-repeat="movie in popular" >
<figure>
<img ng-src="{{movie.backdropURL}}" alt="{{movie.code}}">
<div class="overlay"></div>
<figcaption>{{movie.code}}</figcaption>
<!-- <span class="extra-info">{{movie.extra}}</span> -->
<span class="price">{{movie.price}}</span>
</figure>
</div>
However now I want to have some parent elements that won't repeat but will use the same scope object for their children, that will then repeat.
So, I would like to do a repeater that would append the properties of the scope into their parent, something like this:
<ul class="parent1">
<li><img src={{myScope[0].imgUrl}}></li>
<li><img src={{myScope[1].imgUrl}}></li>
<li><img src={{myScope[2].imgUrl}}></li>
</ul>
<div class="parent2">
<span>{{myScope[0].description}}</span>
<span>{{myScope[1].description}}</span>
<span>{{myScope[2].description}}</span>
</div>
I would like to know if it is possible to reuse a native angular directive (I would prefer not to run the same repeater every time for every parent) where it could append the element to the parent. If not, do you have any suggestion for a solution. I've looked up some links for custom directives I haven't succeeded in applying them. So if you have a 'beginners' custom directive tutorial that could help me go on the right direction, it would be highly appreciated.
I don't know if I understand exactly what you are asking for.
Anyway, the ngRepeat directive is placed in the DOM under a particular parent, so you cannot run it once and append the leaves under different parents. The only way to do that is to create a custom directive that runs a loop internally and sets the leaf under the parent of your choice.
That is:
angular
.module('mymodule')
.directive('mydirective', mydirective);
function mydirective(){
var directive = {
restrict: 'A'
, link: link
}
return directive;
function link($scope, elem, attrs) {
for(var i=0;i<$scope.myScope.length;++i){
var el1 = angular.element('<li><img src='+$scope.myScope[i].imgUrl+'></li>'),
el2 = angular.element('<span>'+$scope.myScope[i].description+'</span>');
elem.find('.parent1').append(el1);
elem.find('.parent2').append(el2);
}
}
}
Please let me know if I misunderstood your goal.
Check this:
HTML:
<div ng-app="myApp" ng-controller="myCtrl">
<ul class="parent1">
<li ng-repeat="item in myScope">
<img ng-src={{item.imgUrl}}>
</li>
</ul>
<div class="parent2">
<p ng-repeat="item in myScope"><span>{{item.description}}</span></p>
</div>
</div>
Controller:
angular.module('myApp', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.myScope = [
{imgUrl:"someUrl1", description: "this is first url"},
{imgUrl:"someUrl2", description: "this is second url"}
]
}]);
Acceptable :) ?
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.
I want to pass the html element into angularjs code. I have some html elements like data-form-selector='#linechart_general_form' and data-url="{% url 'horizon:admin:metering:samples'%}" I want to access these Html elements in angularjs code.
My html templates, what make to change in the following code so that its elements accessible to angularjs code.
<div class="info row detail">
<div class="col-sm-9 chart_container">
<div class="chart"
data-chart-type="line_chart"
data-url="{% url 'horizon:admin:metering:samples'%}"
data-form-selector='#linechart_general_form'
data-legend-selector="#legend"
data-smoother-selector="#smoother"
data-slider-selector="#slider">
</div>
<div id="slider"></div>
<div class="col-sm-3 legend_container">
<div id="smoother" title="Smoothing"></div>
<div id="legend"></div>
</div>
</div>
</div>
--Update-- I just reread your question. To access those ATTRIBUTES (not elements, they are values of an element) you can select the object with an identifier, then manipulate it's attributes, like so (jQuery example):
$('.chart').attr('data-form-selector', 'myNewValue');
You may need to re-initialize sopmething because of the databinding but that's where I'd start.
==================================
You need to create an angular app and controller first, then use the angularjs and ng-model directive to bind the element to a method in your controller, i.e.:
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.firstName= "John";
$scope.lastName= "Doe";
});
</script>
<div ng-app="myApp" ng-controller="myCtrl">
First Name: <input type="text" ng-model="firstName"><br>
Last Name: <input type="text" ng-model="lastName"><br>
<br>
Full Name: {{firstName + " " + lastName}}
</div>
In this case, you'd be manipulating the $scope.firstName and $scope.lastName variables in js.
Courtesy W3Schools
http://www.w3schools.com/angular/angular_intro.asp
You can do a Directive for doing that.
https://docs.angularjs.org/guide/directive
I am a newbie in angularjs. I'm trying to show an html element depending on a property of the $scope object but without using any form element.
This is the snipped of code:
<div id="ListApp">
<div ng-controller="ListCtrl">
{{ myData.prova }}: {{ myData.logged }}
<div id="secretContent" ng-show="myData.logged">
<ul>
<li ng-repeat="elemento in lista">
<input type="checkbox" ng-model="elemento.comprato" />
<span ng-if="!elemento.comprato">{{ elemento.nome }}</span>
<span ng-if="elemento.comprato" style="text-decoration:line-through;">{{ elemento.nome }}</span>
</li>
</ul>
<input type='text' id='input_nome'/><button ng-click="aggiungi()">Aggiungi</button>
</div>
</div>
</div>
And this is the controller part:
<script>
var listaViaggio = angular.module('listApp', ['directive.g+signin']);
listaViaggio.controller('ListCtrl', ['$scope',function ListCtrl($scope) {
$scope.lista = [];
$scope.myData={};
$scope.myData.prova="test";
$scope.myData.logged=0;
$scope.aggiungi = function(){
$scope.lista.push({
'nome':document.getElementById("input_nome").value,
'comprato':false
});
};
$scope.$on('event:google-plus-signin-success', function (event, authResult) {$scope.myData.logged=1;console.log($scope.myData.logged);});
$scope.$on('event:google-plus-signin-failure', function (event, authResult) {$scope.myData.logged=0;console.log($scope.myData.logged);});
}]);
</script>
As can be easily seen, I change the status of $scope.myData.logged on google+ sign in and I espect that the div with id secretContent will be shown or won't be shown depending on this property but I've seen that the truthfulness of the ng-show is evaluated only once and is not binded to the actual value of the property, so, when it changes, nothing happens.
What is wrong in my code? Which is the correct logic flow of the ng-show command and how to bind it to a $scope property?
Thanks in advance to everybody.
Use $scope.$apply() whenever the values get changed.
$scope.$apply() will update the page content.
I'm building my first Angular app, but am having a bit of trouble getting something to work. I have a video container that will be hidden until $scope.video.show = true; I'm trying to set this value when I click on a link. I'm trying to make that happen in a directive. Any help would be appreciated.
html:
<div ng-controller="AppCtrl">
<div ng-cloak
ng-class="{'show':video.show, 'hide':!video.show}">
// youtube iframe content, for example
</div>
<div>
<ul>
<li>
<h3>Video Headline 1</h3>
<button type="button"
video-show
data-video-id="jR4lLJu_-wE">PLAY NOW 〉</button>
</li>
<li>
<h3>Video Headline 2</h3>
<button type="button"
video-show
data-video-id="sd0f9as8df7">PLAY NOW 〉</button>
</li>
</ul>
</div>
</div>
javascript:
var thisViewModel = angular.module("savings-video", [])
.controller('SavingsVideoController', function($scope) {
$scope.video = {
show : false,
videoId : ""
};
};
thisViewModel.directive("videoShow", function(){
return{
restrict: 'A',
link: function(scope , element){
element.bind("click", function(e){
var $this = angular.element(element);
$this.closest('li').siblings().addClass('hide'); // hide the other one
$this.closest('li').removeClass('hide'); // keep me open
scope.video.show = true; // doesn't work.
// what is the best way to do this?
});
}
}
});
I see a few things you can improve.
Checkout ngShow/ngHide and ngIf; they'll give you toggle-ability more easily than trying to do it from scratch.
Think in angular. Rather than trying to use logic to modify the DOM on your own, simply setup your rules using angular directives, and let the framework do the rest for you.
For example, it seems like this is more what you want.
<div ng-controller="AppCtrl">
<div ng-cloak ng-show='video.show">
// youtube iframe content, for example
</div>
<div>
<ul ng-switch="video.videoId">
<my-video my-video-id="jR4ABCD" my-headline="Video Headline 1" ng-switch-when="myVideoId" my-video-manager="video" />
<my-video my-video-id="al1jd89" my-headline="Video Headline 2" ng-switch-when="myVideoId" my-video-manager="video"/>
</ul>
</div>
</div>
What I changed is making your iframe show conditionally with ngShow, and using ngSwitch to control which video appears (the appearing video is based on the $scope's video.videoId). Then, I turned your <li>s into a directive called my-video, which ends up looking like this
thisViewModel.directive("my-video", function(){
return{
restrict: 'E',
replace: true,
scope: {
myVideoId = "=",
myHeadline = "=",
myVideoManager = "="
},
template = '<li><h3>{{myHeadline}}</h3><button type="button" ng-click="play()">PLAY NOW 〉</button></li>',
link: function(scope , element){
scope.play = function(){
myVideoManager.show = true;
/*whatever you want here, using scope.myVideoId*/
}
}
}
});
This directive does exactly what your old HTML did, but brings it into the angular framework so you can access the properties you're looking for. By using the raw angular directives, I eliminate the need for any manual UI logic; I don't need to access element at all anymore, and both my HTML and JavaScript are cleaner. There's certainly room for improvement here, even, but I would say that this is closer to the right track.
It takes practice to get more familiar with, but following the guidelines in the SO link above will help.
EDIT
Sorry, think I missed a requirement the first time around. If you want both videos to show when none are selected, don't use ng-switch; just set up some manual ng-shows.
<div>
<ul>
<my-video my-video-id="jR4ABCD" my-headline="Video Headline 1" ng-show="myVideoId == video.videoId" my-video-manager="video" />
<my-video my-video-id="al1jd89" my-headline="Video Headline 2" ng-show="myVideoId == video.videoId" my-video-manager="video"/>
</ul>
</div>
Since ng-switch is really just a shortcut for ng-show anyways, it amounts to the same thing; the logic just got moved into the ng-show attribute instead.
Also, if you have an array of videos, checkout out ng-repeat; it will let you repeat your video tag multiple times automatically, instead of by hand.
<ul>
<my-video ng-repeat='aVideo in myVideoArray' my-video-id='aVideo.videoId' my-headline...(and so on)>
</ul>
Well your controller names don't match up. Try changing AppCtrl to SavingsVideoController.
You only need a very simple solution.
HTML
<div ng-controller="AppCtrl">
<div ng-cloak ng-show="view.show">
<!-- Use ng-show is more convenient -->
</div>
<div>
<ul>
<li>
<h3>Video Headline 1</h3>
<button type="button"
ng-click="view.show = true"
data-video-id="jR4lLJu_-wE">PLAY NOW 〉</button>
<!-- You don't need an extra directive to change view.show -->
</li>
<li>
<h3>Video Headline 2</h3>
<button type="button"
ng-click="view.show = true"
data-video-id="sd0f9as8df7">PLAY NOW 〉</button>
</li>
</ul>
</div>
</div>
JS
var thisViewModel = angular.module("savings-video", [])
.controller('SavingsVideoController', function($scope) {
$scope.video = {
show : false,
videoId : ""
};
};
// No need to create another directive