Dynamically add, compile and link ng-class attribute from another directive - javascript

In order give feedback on the validity in my input forms I'm using ng-class. These statements will look something like:
<div ng-class="{ 'has-error': !frmSomeName.vcHeader.$valid && frmSomeName.vcHeader.$dirty }">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
I dislike the lengthiness of the statement, and would like to replace it with something alike:
<div validation-state="frmSomeName.vcHeader">
<input type="text" name="vcHeader" ng-model="model.someText" ng-minlength="10" required />
</div>
In order to avoid having to duplicate ngClass' behavior I'd like the the directive to add the ng-class directive.
This plnkr demonstrates my attempt at adding the attribute, and although it works in the simplest scenario, it is faulty and will not function with transclusion (or other more complex directives).
I know it doesn't work because of the misuse of the compile and link stages, however I'm not sure on how to actually make it work properly. Therefore my question: How do I add different directive-attribute from a directive-attribute?

The elements within the transclude directive aren't receiving the same scope. If you use the angualr $compile method and apply the scope of the transclude directive to the scope of the child directives it should work. The following should be added to your simpleTransclude directive:
link: function(scope, element) {
$compile( element.contents() )( scope )
}
remember to pass $compile into the directive.
I've forked your plnkr and applied the simpleTransclude scope to it's contents.

Related

How would I create something similar like this in AngularJS

Having a hard time grasping AngularJs.. How would you build something like this simple textfields and animations in AngularJS? I had been looking into using directives but it just isn't adding up to me much. I had been trying to base this from what I learned online but was not having much luck
http://codepen.io/yusufbkr/pen/RPBQqg
HTML:
<div class="materialContainer">
<div class="title">LOGIN</div>
<div class="input">
<label for="name">Username</label>
<input type="text" name="name" id="name">
<span class="spin"></span>
</div>
<div class="input">
<label for="pass">Password</label>
<input type="password" name="pass" id="pass">
<span class="spin"></span>
</div>
<div class="button login">
<button><span>GO</span> <i class="fa fa-check"></i></button>
</div>
Forgot your password?
<div class="title">REGISTER</div>
<div class="input">
<label for="regname">Username</label>
<input type="text" name="regname" id="regname">
<span class="spin"></span>
</div>
<div class="input">
<label for="regpass">Password</label>
<input type="password" name="regpass" id="regpass">
<span class="spin"></span>
</div>
<div class="input">
<label for="reregpass">Repeat Password</label>
<input type="password" name="reregpass" id="reregpass">
<span class="spin"></span>
</div>
<div class="button">
<button><span>NEXT</span></button>
</div>
just use the codepen link ( http://codepen.io/yusufbkr/pen/RPBQqg ), stackoverflow won't let me input the rest of the code...
Thanks! Any help would be incredible
I'm just learning Angular myself. From what I know, you're on the right track to be thinking of directives for swapping out interactive parts of the DOM. I suspect you'll also be needing ui-router for dealing with the widget outcomes (I hear it's the industry standard, vs the built-in ngRouter).
I'm partly writing this out to make sure I understand it, so I hope other, more knowledgeable people will come by and answer any questions you have about it (or point out where I'm misundertanding something.)
So you bring the webpage into the Angular world by, for instance, making the <html></html> tags into <html ng-app="yourApp"></html>. The unchanging html that will be the same on every view is in the index.html file. Put the html for the boxes is in another html file, say box.html. On the index.html file in the place where you want to have your boxes appear, put <div ui-view></div>. The ui-view connects to ui-router. Down at the bottom of the index.html right before closing the body tag, put
<script src="lib/angular/angular.js"></script>
<script src="lib/angular-ui-router/release/angular-ui-router.js"></script>
<script src="app/app.js"></script>.
In app.js, you put something like,
angular.module('yourApp', [
'yourApp.box',
'ui.router'
])
.config(function($stateProvider, $urlRouterProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/signin/child');
$stateProvider
.state('box', {
url: '/signin',
templateUrl: 'app/box/box.html',
controller: 'BoxController'
})
.state('box.child', {
url: '/child',
template: '<span>{{ definedProperty }}</span>'
});
})
});
Starting at the top, you name angular, call its module function, and give it the parameters ('theNameYouChose', ['dependencies','required'])
In the config bit there, you're telling it about dependencies config needs. $stateProvider and $urlRouterProvider are ui-router things, and $httpProvider is, I believe, an angular thing that is still necessary.
In the urlRouterProvider, you're providing the default address the website goes to: in this case it happens to be a template within a template, /signin from the signin state, and /child from the specifics pasted into signin
In the states, you are giving names and properties to the URLs that will be part of your app: like, if someone goes to index.html/signin, the 'signin' state is summoned.
The template within a template might be how you'd get the box variability that you're looking for. In box.html you will have another <div ui-view></div> and that's where the varying thing described in 'signin.child' gets put in.
box.html is also the place you put your specially created html tags, the ones you will make with the directive. I'll talk through those below.
The parent template, 'signin', talks about BoxController. Let's say you built that in box.js. It would look like this:
angular.module('yourApp.box', [])
.controller('BoxController', function ($scope) {
$scope.definedProperty = 'reRegPass',
$scope.arrayOfObjects = [{prop: 'red'},{prop: 'blue'}]
})
.directive('specificBox', function(){
return {
restrict: 'EA',
templateUrl: 'app/box/box-guts.html',
replace: true,
scope: {
source: '='
},
link: function(scope, element, attribute) {
element.on('click', function() {
alert('Functionality in specificBox is working');
}
}
}
});
The first line is again summoning angular module, and then naming it: note we already listed this, 'yourApp.box', as a requirement in app.js. Then we have the controller-naming function, and the appearance of specific properties in their $scope.
The directive is taking the more useful of the two forms a directive can take. They can either return functions (with signatures like the one in .link), or they can return objects that describe new HTML entities. This one is named specificBox, which Angular will translate into <specific-box></specific-box>. This object (technically called a 'directive definition object') can be translated into HTML tags because of the restrict property... 'E' is for element. 'A' is for attribute. I don't know how something that can be an element could also be an attribute, but having both options works, so I'm going with it. (Maybe for your various signin boxes you want a directive that has just 'A' to make new types of attributes.)
You put these specifically-crafted tags into box.html, and everything in box-guts.html will be between those tags... In fact, replacing those tags because of the replace: true.
The .link property is where you would put a function that would do something interesting to that element; turn it red if clicked, whatever. I have an alert example. Uses jQuery Lite for events.
The .scope property is odd. I believe if left off, the specific-box stuff would have the same controller as box (ie, BoxController), with box's scope and dependencies. But here, instead, we're making an isolate scope. box-guts will not depend on anything or have access to anything built before. How will it get interesting changeable data, then..? Every property in the scope object becomes an attribute on specific-box.
The '=' means that you will pass it objects from somewhere, and they will have 2-way data binding (changes made in box-guts will be reflected in the object collection in your app). Other options are '#', one-way data binding, which means you're passing box-guts a string or something that, if it changes in the DOM, you don't care to reflect in your app; or '&' to give it a function from a controller somewhere else in your app.
So ultimately, in box.html, you will have something like
<div class="box-holder">
<div ui-view></div>
<specific-box source="thing" ng-repeat="thing in arrayOfObjects"></specific-box>
</div>
Like I said above, ask questions and maybe we can sort this out.

Two way binding of form input directive with isolated scope

I am leaning AngularJS, specifically working on learning directives. Would like to have a form input directive that I can re-use on all my forms to encapsulate all the boiler plate markup. Though I am having trouble getting two way binding to work in my directive. It is using an isolated scope with its own internal property to store the value of the input field. I've setup a watch on that internal property that is correctly pushing the value from within the isolate scope up to the controllers scope. What I am trying to figure out is how to take an initial value from the controllers scope and set it as the initial value in my directive.
Plunker Link: http://embed.plnkr.co/TbVB0q9DHhBCVLQ4U64W/script.js
Typing in the first input box changes the controller scopes property, but not the directives value. Typing in the second input box changes the directive and the controller property.
I know this is possible using an attribute to pass the initial value. However I am hoping to be able to extract the value from the controller scopes property via the ngModel reference in my directive.
EDIT AFTER ANSWER:
For those not sure about why you want to go through the effort of learning directives. Especially when Angular is so powerful even without using directive. This is one good reason why.
Input fields on my form without directive:
<div class="form-group">
<label for="firstName" class="col-sm-6 col-md-4 control-label">First Name</label>
<div class="col-sm-6 col-md-8" ng-class="{'has-error': userForm.firstName.$invalid}">
<input type="text" id="firstName" name="firstName" placeholder="First Name" ng-model="muState.currentUser.firstName" class="form-control" required
popover="Cannot be blank" popover-trigger="{{{true: 'mouseenter', false: 'never'}[userForm.firstName.$invalid]}}" />
</div>
</div>
After using my directive:
<ws-form-input input-name="firstName" input-label="First Name" input-placeholder="First Name"
ng-model="muState.currentUser.firstName"
required input-error="Cannot be blank"></ws-form-input>
Go through the effort. You'll be trading the headaches of learning versus a maintenance nightmare later.
I think you can simplify your directive altogether by using the isolated scope's '=' attribute notation.
Something like this:
JAVASCRIPT
app.directive('inputDirective',function(){
return {
restrict: 'E',
replace: true,
scope: {ngModel: '='},
templateUrl: "directiveTemplate.html",
require: '^form',
link: function(scope, elem, attrs, ctrl){
scope.form = ctrl;
scope.required = false;
// If attribute required exists
if (attrs.required !== undefined) {
// ng-required takes a boolean which is read from this scope variable
scope.required = true;
}
}
};
});
HTML DIRECTIVE
<div>
<input type="text" id="directiveInput"
ng-model="ngModel" class="form-control" ng-required="required"/>
<br/>
Isolated Scope value of the input box: {{ngModel}}
</div>
See UPDATED PLUNKER
You have to implement ngModel.$render, see ref here.
The following code in your directive will suffice:
scope.ngModel.$render = function() {
scope.inputBinding = scope.ngModel.$viewValue;
};

Bind ng-models in input type checkbox

I have a problem when binding ng-models with ng-repeat in a input tag type checkbox.
I will first attach my code and then explain more in detail.
app/main.html:
<div ng-repeat="feature in features">
<input type="checkbox" ng-model="features[$index].name">{{features[$index].name}}
</div>
<br></br>
<div class="highlighter">
<span ng-class="{emo:Emotions}">Manually</span> <span ng-class="{feel:Feelings}">create</span> the <span ng-class="{emo:Emotions}">entire</span>
</div>
main.js
angular.module('webClientApp')
.controller('MainCtrl', function ($scope,$http) {
[...other variables...]
$scope.features = [{'name':'Emotions'},{'name':'Feelings'}];
[...other parts of code]
});
Let's also assume that in the main.css file there are references to the classes .emo' and.feel' respectively to highlight the target word when the user ticks the box relative to the feature.
Now, the application works correctly when I listed all the inputs one by one like the following:
<input type="checkbox" ng-model="Emotions">Emotions
<input type="checkbox" ng-model="Feelings">Feelings
but I wanted to wrap it into an ng-repeat and list the features in the controller scope, since the features I will considered will be more. When I try the code above when I tick on the box the name changes to `true'.
I have read a lot about how to bind models to an ng-repeat inside a input tag but none of the solutions apply to my case.
Can someone please help me?
I changed thigs up quite a bit from your original model but... I did get something to behave similar to what you are looking for.
HTML
<div ng-app="webClientApp">
<div ng-controller="MainCtrl">
<div ng-repeat="(feature,enabled) in features">
<input type="checkbox" ng-model="features[feature]">{{feature}}</input>
</div>
<div class="highlighter">
<span ng-class="{emo:features.Emotions}">Manually</span> <span ng-class="{feel:features.Feelings}">create</span> the <span ng-class="{emo:features.Emotions}">entire</span>
</div>
{{features}}<br>
{{features.Emotions}}<br>
{{features.Feelings}}
</div>
JS
var app = angular.module('webClientApp', []);
app.controller('MainCtrl', function($scope, $http) {
$scope.features = {Emotions: true, Feelings: true};
});
Here's the fiddle: http://jsfiddle.net/rodhartzell/8YrxQ/
Hope this helps.
(i should add this as a comment, but I don't have enough rep. yet)
There is an issue on github which concerns your issue: https://github.com/angular/angular.js/issues/1404 and the comment of caitp shows some workarounds: https://github.com/angular/angular.js/issues/1404#issuecomment-30859987
You could (also) define a new javascript object in your controller and map the elements to that.
In controller: $scope.awnsers = {};
In template: ng-model="awnsers[feature.name]"
I hope this helps
You must use ng-checked instead of ng-model.
Check out this jsfiddle
http://jsfiddle.net/fizerkhan/z5z9s/24/
ngModel and ngChecked are not meant to be used together.
ngChecked is expecting an expression, so by saying ng-checked="master". If the expression is truthy, then special attribute "checked" will be set on the element
You should be able to just use ngModel, tied to a boolean property on your model. If you want something else, then you either need to use ngTrueValue and ngFalseValue (which only support strings right now), or write your own directive.

angularjs: Change parent scope from controller within a ng-switch

So, I can change a model value from a child controller, but when the child controller is in ng-switch then it doesn't work, why? I created an example to demonstrate it.
One way to avoid this is to use the . in the model name, like bunnies.kills. Is this a bug or this is a feature ?
Using Angular 1.0.6
Using your code structure, in your child controllers you would need to change:
$scope.$parent.kills++;
to
$scope.$parent.$parent.kills++;
Explanation: MainCtrl's scope is the parent scope of SimpleParentCtrl, but the grandparent of Step1Ctrl and Step2Ctrl. As some others pointed out, ng-switch creates its own scope, and then your Step1Ctrl and Step2Ctrl each created a child scope of the ng-switch.
Note: Each time the 1 or 2 button is clicked, both the ng-switch and it's currently matched child controller get a new scope.
Also: In case you happen to be looking in the Angular source and wondering how the ng-switch directive creates its own scope without a scope property, the answer is that it does so manually in its link method via scope.$new(). The directives ng-include, ng-switch, ng-repeat, and ng-view all create new scope this way, either in the link method or the compile method's returned link function.
Resources:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
http://www.youtube.com/watch?v=ZhfUv0spHCY&feature=youtu.be&t=30m
ng-switch creates its own child scope, which is why #sh0ber's answer is one way to get it to work. In general, models should be referenced in controller scopes (hence reference objects), and not be not primitives. So using a . is a "best practice".
This is not a bug, but it is not a feature either. This is the way JavaScript prototypal inheritance works with primitives.
I would take a slightly different approach to this problem.
Rather than use $scope.$parent, I would recommend you move all of your bunny killing logic into a shared service/model.
Also, I would try to avoid referencing parent views/controllers. Referencing the parent can make it difficult to reuse your code and can be painful to debug as the project grows. It is okay for a parent to know about it's children but a child should know little to nothing about it's parent.
Here is an updated Plunk: http://plnkr.co/edit/PLDbfU8Fu7m59A42qdR6?p=preview
HTML
<body ng-controller="MainCtrl">
<p>
Dead bunnies: <strong>{{Elmer.deadWabbits}}</strong>
</p>
<div ng-controller="SimpleParentCtrl">
<button ng-click="Elmer.killTheWabbit()">Kill from simple parent gun</button>
</div>
<hr>
<div ng-switch="" on="step">
<div ng-switch-when="first" ng-controller="Step1Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 1 tab gun</button>
</div>
<div ng-switch-when="second">
<div ng-controller="Step2Ctrl">
<button ng-click="Elmer.killTheWabbit()">Kill from 2 tab gun</button>
</div>
</div>
</div>
<hr>
<p>
<button ng-click="changeStep('first')">1</button> <button ng-click="changeStep('second')">2</button>
</p>
</body>
JS
angular.module('plunker', []).
service("Elmer", [function() {
this.deadWabbits = 0;
this.killTheWabbit = function() {
this.deadWabbits++;
};
}]).
controller('MainCtrl', function($scope, Elmer) {
$scope.Elmer = Elmer;
$scope.step = 'first';
$scope.changeStep = function(name){
$scope.step = name;
};
}).
controller('SimpleParentCtrl', function() {}).
controller('Step1Ctrl', function() {}).
controller('Step2Ctrl', function() {});
One way to avoid this is to use the . in model name, like bunnies.kills. Is this a bug or this is a feature ?
This has been explained numberous times : https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
and in mhevery's video

Bind tag name in AngularJs

I want to bind tag name to variable in AngularJs. Direct way doesn't work:
<div ng-app ng-init="list=['pre', 'div', 'em']">
Check the list: {{list}}
<div data-ng-repeat="item in list">
{{item}}: <{{item}}>content</{{item}}>
</div>
</div>
How to do it right?
​
You're going to want to make a Directive and use the $compile service module.
Angular template system works on DOM tree, not on strings, so template must be valid HTML and usage of {{}} for tagname is impossible. We can write own directive for it (see Max answer) or if here is small set of options it can be more easy to use ng-include and set of templates for options.

Categories