how can i use ngModel inside bindings of components angularjs - angularjs

angular.module('RatingModule').component('starRating', {
templateUrl: 'rating.html',
controller: RatingCtrl,
bindings: {
maxStars: '=',
ngModel : '='
//above is the component where i want to use ngModel inside bindings of coponents
function RatingCtrl() { i want to use ngModel here }
// below is the index.html where ng-model is binding a value from the app controller
<body ng-app="RatingModule" ng-controller="appCtrl as ctrl">
<star-rating ng-model="ctrl.rating.number" max-stars="5"></star-rating>

the angular ngModel directive binds an input,select, textarea (or
custom form control) to a property on the scope using
NgModelController, which is created and exposed by this directive.
This sould not work, plus you should not use a angular name for a custom property.
you can do
angular.module('RatingModule').component('starRating', {
templateUrl: 'rating.html',
controller: RatingCtrl,
bindings: {
maxStars: '=',
myModel : '='
then
function RatingCtrl() { $scope.myModel }
and
<star-rating my-model="ctrl.rating.number" max-stars="5"></star-rating>

Well, actually it's possible to use ngModel, but it's 100% worthless.
You can set ng-model on component, then require: 'ngModel' and manually set ngModelCtrl.$setViewValue on 'click' for example. But as I said before, it's ill wrong approach.
Here is a Plunker, basically, you can set model as you want
element.on('click', function(){
ngModelCtrl.$setViewValue(scope.index);
});
$setViewValue(value, trigger); Update the view value.
This method should be called when a control wants to change the view
value; typically, this is done from within a DOM event handler. For
example, the input directive calls it when the value of the input
changes and select calls it when an option is selected.

DMCISSOKHO raised a good point, you couldn't use ng-model here. Try this instead:
angular.module('RatingModule').component('starRating', {
templateUrl: 'rating.html',
controller: RatingCtrl,
bindings: {
maxStars: '=',
ratingNumber : '='
and in your view use
<star-rating rating-number="ctrl.rating.number" max-stars="5"></star-rating>

Related

AngularJS 1.5 - How to Set Two Way Bindings on Component

Directives in Angular 1.X are set to have two way binding by default. Components have isolated scopes by default. I have a view that looks like:
<div class="my-view">
{{controllerVariable}}
</div>
If I have the above set up as a directive, the controllerVariable loads correctly in the following situation:
<div ng-controller="myController">
<my-view></my-view>
</div>
But if I have it set up as a component using the following:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: '='
});
then the variable value isn't displayed. I have tried adding $ctrl to the variable:
<div class="my-view">
{{$ctrl.controllerVariable}}
</div>
but this doesn't display the value either.
What am I missing here?
You need to pass the value from the directive into the component:
<my-view passed-var='ctrl.passedVar'></my-view>
and in the component:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: {
passedVar: '='
},
controller: function () {
var vm = this;
console.log(vm.passedVar);
}
});
then you will be able to access in the component as in the example
There are a few other ways to do it, such as using a service to handle the information or using require which would give your component access to the controller of the directive. You can find the above method and others here: https://docs.angularjs.org/guide/component.
I had to explicitly state the variable I wanted to bind:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: {
controllerVariable: '#'
}
});
Also, since controllerVariable is a string, I had to use the # sign binding.

one way object from controller to directive

I want to pass object from controller to directive but not two way binding.
I tried with JSON.stringify and it work but in HTML it display whole data.
Is there any way around to achieve this?
In my controller:
$scope.obj= { selectedItems=[1,2,3]};
In html:
<my-dir pass-obj= "obj"><my-dir>
In directive:
scope:{passObj='#'}
It give me string as "obj" not the object.
In older versions of AngularJS one-way binding is done with $watch.
app.directive("myDir", function() {
return {
scope: {},
link: function(scope, elem, attrs) {
//one-way binding
scope.$parent.$watch("attrs.passObj", function(newValue) {
scope.passObj = newValue;
});
}
};
});
The above example is the equivalent of doing one-way binding with AngularJS 1.5:
scope: { passObj: '<' }
This should work..
controller:
$scope.obj= { selectedItems=[1,2,3]};
html:
<my-dir passObj= "::obj"><my-dir>
It's been asked before, and you can read more here; One-way binding in Angular directives

Two way binding from the link function

Can someone tell me why I am not able to two way bind from the link function?
Please refer to this plunk: http://plnkr.co/edit/RI1ztP?p=preview
The below watch successfully adds the collection to attrs.ngModel but I dont see it reflecting in the parent controller
scope.$watchCollection("selectedItems",function(collection){
attrs.ngModel = [];
for(var i=0;i<collection.length;i++){
attrs.ngModel.push(collection[i]);
}
console.log("ngModel",attrs.ngModel);
});
Cant see the collection over here (selectedUsers):
<body ng-controller="mainCtrl">
<div multi-select-search-box ng-model="selectedUsers" label="name" my-options="state in states"></div>
{{selectedUsers}}
If you look at the above html, I am binding the selectedUsers array to ng-model. In my link function, i add the selected users to attrs.ngModel array. When I look at the console, the selectedUsers are added to attrs.ngModel but the array isn't reflected back on the html {{selectedUsers}}
The data bound to the ng-model of your multi-select-search-box is $scope.selectedUsers.
Therefore to register a change in the DOM you have to update that variable rather than ng-model.
scope.$watchCollection("selectedItems",function(collection){
for(var i=0;i<collection.length;i++){
scope.myNgModelVar.push(collection[i]);
}
});
Since ng-model is a string that gets $parse()/$eval() called on it to evaluate it as an expression, updating that ng-model value won't do you any good.
EDIT:
After some clarification it appears that this is a custom directive designed to be reusable. So therefore we do not want to stick variables from your controller inside the directive. Instead, you should bind a directive attribute to your directives scope.
// Directive Def. Object:
return {
restrict: "AE",
scope: {
myNgModelVar: "=",
bindModel: "=ngModel" //This is the alternate method aliasing ngModel var with a scope var.
},
template: "<input ng-model='myNgModelVar' />"
};
Although you could use ngModel by using an alias scope: {bindModel:'=ngModel'}, this gives you an isolated scope variable that you bind to ngModel instead. Therefore keeping your directive reusable.
The solution was to require the ng-model controller and sync changes using the viewValue array:
scope.$watchCollection("selectedItems",function(collection){
ctrl.$viewValue.splice(0,ctrl.$viewValue.length);
for(var i=0;i<collection.length;i++){
ctrl.$viewValue.push(collection[i]);
}
});
and
require: 'ngModel'

Passing ng-repeat context to child directive

In parts of my application I have used a directive in this format:
<child-directive ng-repeat="item in vm.items"></child-directive>
This one has access to {{item}} from within child-directive without having to do anything.
Now I want to use the same directive along side other directives that all work with the same context data:
<div ng-repeat="item in vm.items">
<child-directive></child-directive>
<other-directive></other-directive>
</div>
The child directive does not need to alter the context data, it only needs the information from inside to display a widget.
I've tried using scope in the directive in this format:
angular
.module('myapp.dashboard')
.directive('childDirective', childDirective);
function childDirective() {
var directive = {
restrict: 'E',
templateUrl: 'client/components/child-directive.ng.html',
controller: 'ChildDirectiveController',
controllerAs: 'vm',
scope: {
item: '='
}
};
return directive;
}
and
<child-directive ng-attr-item="{{item}}"></child-directive>
Within the ng-repeat section. However that just throws an error.
I've also tried ng-bind with no luck.
Any suggestions?
You're using bi-directional scope binding rather than an interpolated property. You can read up on this more in the angular docs
Change your scope object to be:
scope: {
item: '#'
}
OR, change your template to:
<child-directive ng-attr-item="item"></child-directive>

In angular is it possible to move a $scope property to controller scope?

I'm now creating a nested controller, with the parent controller passing a variable in, like this:
app.directive('entity', function() {
return {
restrict: 'E',
controller: 'EntityShowController',
templateUrl: '/show.html',
controllerAs: 'showCtrl',
scope: {
entity: '=' // two way binding parent/child
}
};
})
And in the template I have:
<entity entity="parent.getSelected()"></entity>
Now in the child controller I can do this:
app.controller('EntityShowController', function($scope) {
// this is what I should do to access the passed in two-way sync entity
console.log($scope.entity);
// this is what I like to achieve
this.entity = $scope.entity;
$scope.entity=null;
}]);
Is it possible to set a controller local data (this property) to track the parent controller data (the $scope property)?
I know that I can implement a setEntity method combined (e.g.) with ng-click but this is not exactly what I'm trying to achieve.
Provided you are using 1.3.x version of angular you can set bindToController flag on the directive settings to say bind the 2-way bound scope properties to the controller instance, if you are below 1.3.x this options is not available and you would need to either directly work on scope or you would need to establish a synchronization mechanism to sync between controller instance and scope property.
.directive('entity', function() {
return {
restrict: 'E',
controller: 'EntityShowController',
templateUrl: '/show.html',
controllerAs: 'showCtrl',
bindToController:true,
scope: {
entity: '=' // two way binding parent/child
}
};
})

Resources