Is there a way in angular to get binding back from a template?
In other words, if I have something like this:
<div ng-repeat="item in list">
<div>{{item.name}}</div>
<div>{{item.state}}</div>
</div>
would it be possible to change the item's state by clicking on it, because the repeated div would "remember" what item it was built from?
Yes, you can use the ng-click directive to trigger a method on the current scope:
// In your view's controller:
function MyCtrl($scope, MyList) {
// You probably have something like that already to
// populate your list, using a $resource or $http GET call.
// Here I use a $resource which would be defined on your module.
$scope.list = MyList.query()
$scope.setState = function(state) {
// "this" refers to the current scope
this.item.state = state
}
}
// And in your view:
<div ng-repeat="item in list">
<div>{{item.name}}</div>
<div ng-click="setState('whatever')">{{item.state}}</div>
</div>
Or you can simply set an expression such as ng-click="item.state='whatever'" directly on the div, although this is less testable - only in end-to-end tests - and less flexible, say you want to introduce validation or something).
HTH
Related
In the ma-resource-text-watch Directive I make an api call to get a list of resource texts.
I want to be able to hide the alert-component if the api doesn't return any resource texts.
Does anyone know how I might be able to do this?
<div ng-controller="IntroductionCntrl" class="hidden-print">
<div class="container-fluid" ng-if="introductionResourceKey">
<alert-component type="guidance">
<span ma-resource-text-watch="{{introductionResourceKey}}"></span>
</alert-component>
</div>
</div>
You can have your directive take in a callback (or expression), which it will fire when the data is loaded. For example, in the directive definition, the scope property can have:
scope: {
onTextsLoaded: '&'
}
The directive can then call:
scope.onTextsLoaded({ texts: yourTexts })
And the parent controller can pass in an expression as a callback, and use ng-show to hide the alert-component:
<alert-component ng-show="dataIsLoaded && texts.length">
<span ma-resource-text-watch="{{introductionResourceKey}}" on-texts-loaded="onTextsLoaded(texts)"></span>
</alert-component>
With the function defined like:
$scope.onTextsLoaded = function(texts) {
$scope.dataIsLoaded = true;
$scope.texts = texts;
}
Trying to make a rating directive but I'm stuck at getting rating2 to work. The first rating worked because the rating1 is hardcoded within the controller. But normally I have to get the saved rating from the db, which I'm trying to do with rating2, as u can see the value is fetched but the directive is not appearing.
https://codepen.io/eldyvoon/pen/MbBNLP
<div star-rating ng-model="rating.rating1" max="10" on-rating-select="rating.rateFunction(rating)"></div>
<br>but rating2 is actually there:
{{rating.rating2}}
<star-rating ng-model="rating.rating2" readonly="rating.isReadonly"></star-rating>
Need expert of directive to help.
Initiate rating2 :
function RatingController($http) {
this.rating1 = 5;
this.rating2 = 0; //ADD THIS LINE
var self = this;
it works for me
check here
First of all, I'm not a directive expert but i'm trying to help. I think that when html is first load, the values from db not finish execute and bind into html. The best way is not using directive instead using controller to fetch data from db.
You pass a model without rating2 into your directive and the changes from the parent controller won't affect it, because variable is created afterwards. Adding a watcher in your linker on parent scope will solve the problem;
scope.$parent.$watch('', function(rating){
updateStars();
});
Other solution would be to define a starting value in your controller.
this.rating2 = 1;
Notice that it is bad design to have a scope variable for each rating. It is cleaner to have an array of ratings and you actually do not need the watcher by doing so.
https://codepen.io/hoschnok/pen/LbJPqL
angular controller
function RatingController($http) {
this.ratings = [4];
var self = this;
$http.get('https://api.myjson.com/bins/o0r69').then(function(res){
self.ratings.push(res.data.rating2);
});
}
HTML
<div ng-app="app" ng-controller="RatingController as rating" class="container">
<div ng-repeat="r in rating.ratings">
<div star-rating ng-model="r" max="10" on-rating-select="rating.rateFunction(rating)"></div>
</div>
</div>
The watcher change handler function has parameters reversed:
//INCORRECT parameters
//scope.$watch('ratingValue', function(oldValue, newValue) {
//CORRECT parameters
scope.$watch('ratingValue', function(newValue, oldValue) {
if (newValue) {
updateStars();
}
});
The first argument of the listening function should be newValue.
The DEMO on CodePen
ALSO
The ng- prefix is reserved for core directives. See AngularJS Wiki -- Best Practices
JS
scope: {
//Avoid using ng- prefix
//ratingValue: '=ngModel',
ratingValue: '=myModel',
max: '=?', // optional (default is 5)
onRatingSelect: '&?',
readonly: '=?'
},
HTML
<!-- AVOID using the ng- prefix
<star-rating ng-if='rating' ng-model="rating.rating2"
max="10" on-rating-select="rating.rateFunction(rating)">
</star-rating>
-->
<!-- INSTEAD -->
<star-rating ng-if='rating' my-model="rating.rating2"
max="10" on-rating-select="rating.rateFunction(rating)">
</star-rating>
When a custom directve uses the name ng-model for an attribute, the AngularJS framework instantiates an ngModelController. If the directive doesn't use the services of that controller, it is best not to instantiate it.
I have a custom form application written in AngularJS and now I need to use the data from the form in a template. But nothing I've tried seems to work.
I am creating a custom directive like this...
.directive('dynamicModel', ['$compile', function ($compile) {
return {
'link': function(scope, element, attrs) {
scope.$watch(attrs.dynamicModel, function(dynamicModel) {
if (attrs.ngModel == dynamicModel || !dynamicModel) return;
element.attr('ng-model', dynamicModel);
if (dynamicModel == '') {
element.removeAttr('ng-model');
}
// Unbind all previous event handlers, this is
// necessary to remove previously linked models.
element.unbind();
$compile(element)(scope);
});
}
};
}])
This is attached to a form element like this..
<div class="row" ng-repeat="field in customForm.fields">
<label>{{field.displayname}}
<input class="form-control" type="{{field.type}}" name={{field.variable}} dynamic-model="field.databind" placeholder="{{field.variable}}" required="{{field.isRequired}}"></label></div>
This part works great, the field is now 2 way bound to the input form.
However when I later tried to use the same method to show the value in a report computed from the form, I get "field.databind" or at best the resolved databind field name such as "currentUser.name" rather than the value, e.g. "devlux"
I've tried
<div class="row" ng-repeat="field in customForm.fields">
<p>{{field.name}} = {{field.databind}}</p>
Also
<p dynamicModel="field.databind"></p>
</div>
Nothing works unless I put it into an input element, which isn't what I'm trying to do here.
The dynamic model code was pulled off someone elses answer to a question about creating dynamic form elements, and honestly I think it's just a step beyond my comprehension. But assuming that "field.databind" will always be a string literal containing the name of an inscope model, how on earth do I access it in a normal template?
{{field.databind}} will be evaluated against the current $scope and will result in whatever $scope.field.databind is, for example the string currentUser.name.
Angular has no way of knowing that currentUser.name isn't the string you want, but actually another expression that you want to evaluate.
To evaulate it again you will need to add a function to your $scope that uses the $parse service.
For example:
$scope.parseValue = function (value) {
return $parse(value)($scope);
};
In HTML:
<div class="row" ng-repeat="field in customForm.fields">
<p>{{field.displayname}} = {{parseValue(field.databind)}}</p>
</div>
The argument that gets passed to parseDynamicValue will for example be currentUser.name. Then it uses the $parse service to evaulate the expression against the current $scope, which will result in for example devlux.
Demo: http://plnkr.co/edit/iPsGvfqU0FSgQWGwi21W?p=preview
End goal: Associate multiple email addresses, each with a frequency setting (daily,weekly,monthly), to a notification.
I am attempting to define a directive which acts on a button element, such that, when the button is clicked, it takes the email address from the input element and the frequency from the drop-down next to the button and inserts them into a list below the input+button and binds the dynamic list to a property on the controller so it can be sent to the server when the user submits the form.
The form is all wired up - thats not the question.
I just want some help in building the directive.
Here's what i have so far:
HTML:
<section ng-controller="notificationmanagement as vm">
<input name="email" type="email"/>
<select>
<option>Daily</option>
<option>Monthly</option>
<option>Weekly</option>
</select>
<button data-cm-click type="button" class="btn btn-default"></button>
<div ng-model="vm.Subscribers"></div>
</section>
Directive:
commonModule.directive('cmClick', function () {
return function (scope, element) {
element.bind("click", function () {
// Is this the best way to get the value of the EmailAddress & Frequency?
var emailAddress = element[0].parentElement.parentElement.children[0].value;
var frequency = element[0].parentElement.parentElement.children[1].value;
var spanEmailTag = angular.element('<span>' + emailAddress + '</span>');
var spanFreqTag = angular.element('<span>' + frequency + '</span>');
angular.element(spanEmailTag).appendTo(element[0].parentElement.parentElement);
angular.element(spanFreqTag).appendTo(element[0].parentElement.parentElement);
// How to make sure each added email+frequency is available
// in the array bound to 'vm.Subscribers'
});
}
});
The structure of 'vm.Subscribers' should be something like this, in order for it be consumed by the server:
vm.Subscribers = [ {'EmailAddress':'joe#email.com', 'Frequency':'Daily'}, {'EmailAddress':'bob#email.com', 'Frequency':'Monthly'} ];
NOTE: I would ideally like to achieve this without relying on jQuery within the directive.
Any and all pointers/help/advice would be most appreciated!
If you want to encapsulate and reuse some functionality, then, by all means, use a directive.
But first, understand the MVVM (Model-View-ViewModel) paradigm and how Angular implements this.
For starters, assume that there is no View (and so, no DOM, directives, etc...) and that user inputs magically occur when something is exposed on the $scope (or, if you are using ControllerAs - as a property of the controller). Now, build you app's functionality starting from the controller. Define the data structure and the behavior with functions.
app.controller("notificationmanagement", function(){
// list of subscribers. You could also populate it from the backend.
this.subscribers = [];
// this is where the details of a new subscriber will be stored
// until subscriber is added
this.newSubscriber = {EmailAddress: null, Frequency: null};
// action to add a susbscriber
this.addSubscriber = function(){
this.subscribers.push(this.newSubscriber);
// clear the object (it's not necessary to declare all the properties)
this.newSubscriber = {};
}
});
That is, in a nutshell, all your app is doing. The controller doesn't (and shouldn't) care how this is displayed in the View. This is why DOM manipulation is frown upon, because it breaks separation of concerns.
Now, to the View. The View is mostly declarative:
<section ng-controller="notificationmanagement as vm">
<input ng-model="vm.newSubscriber.EmailAddress" type="email>
<select ng-model="vm.newSubscriber.Frequency">
<option value="Daily">Daily</option>
<option value="Monthly">Monthly</option>
<option value="Weekly">Weekly</option>
</select>
<button ng-click="vm.addSubscriber()"> Add </button>
<hr/>
<h3>All subscribers:</h3>
<div ng-repeat="s in vm.subscribers">
<span>{{s.EmailAddress}}</span>
<span>{{s.Frequency}}</span>
</div>
</section>
Notice how ng-model directives bind input data to controller's data. Notice ng-click that invokes an action on the controller. Notice ng-repeat that iterates and creates DOM elements based on that data. The View is purely driven by data, which is referred to by ViewModel.
Understand this first, then move onto directives.
I am new to Angular getting stuck after making ajax call. How do I render/compile the html content once you inject in DOM so that I can still use the AngularJs functions.
Due to the way my backend is set up I have to get content via ajax ($http). And I am making the app without jQuery. I tried $compile and $apply but didn't work. What am I missing here.
I have the code set up at http://jsfiddle.net/rexonms/RB7FQ/3/ . I want the second div content to have the same properties as the first div.
HTML
<div ng-controller="MyCtrl" class="section">
<input ng-model="contentA">
<div>
And the input is: {{contentA}}
</div>
</div>
<div ng-controller="MyAjax" class="section">
<div id="dumpAjax">
{{ajaxData}}
</div>
<button ng-click=getajax()> Get Ajax</button>
</div>
SCRIPT
var myApp = angular.module('myApp',[]);
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
}
function MyAjax($scope){
var data = '<input ng-model="contentB">{{contentB}}';
$scope.getajax = function(){
$scope.ajaxData = data;
}
}
Thanks in advance.
ng-bind-html-unsafe is not available 1.2 and later verison of angular...
so you should use ng-bind-html which creates a binding that will innerHTML the result of evaluating the expression into the current element in a secure way.
using $scope variable in your string make it unsafe, so you should use $sce.trustAsHtml but this time variables in your string cannot be bind because they will be not compiled...
basically you should compile your string in order to bind your variables. Here comes custom directives you can create a directive which can replace with ng-html-bind...
Writing a custom directive which extends ng-bind-html with some extra functions can be a solution...
here is my PLUNKER
and here is your updated JSFIDDLE with my solution...
Instead of {{ajaxData}}, you should use something like:
<div ng-bind-html-unsafe="ajaxData"></div>
However, you'd still need to set the proper model to bind the contentB and get it working.