AngularJS two way data binding in directive not working - angularjs

I have a directive that updates a bound property, but it never seems to update the original property!
directives.directive('recordVideo', [function () {
return {
scope: {
showRecordVideo: '='
},
controller: "recordVideoController as ctrl",
templateUrl: '/views/recordvideo.html'
};
}]);
<record-video data-show-record-video="showAddScheduleDialog"></record-video>
When I set $scope.showAddScheduleDialog = true in the parent controller, the directive sees the change and shows the dialog. When the dialog itself sets its property $scope.showRecordVideo = false the bound property on the parent controller showAddScheduleDialog never updates!
Why is this?
I have tried putting $scope.$watch on both the parent controller and the directive. The changes only propogate down to the directive and never back up to the controller!

The problem is caused by javascript prototype inheritance (the long answer). The usual hack is to change a property inside:
This stays the same:
scope: {
showRecordVideo: '='
},
In controller:
$scope.showRecordVideo = {
state: true
};
In modal:
$scope.showRecordVideo.state = false;

Related

how can i use ngModel inside bindings of components 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>

Angularjs directive two-way bound variable changes are not triggering $digest on the parent scope

Apologies if some of the JS is syntactically off. I wrote it while looking at my CoffeeScript
I have a text editor that I've extracted into a directive and I want to share some state between it and its containing template:
Main containing template
<div class="content">
<editor class="editor" ng-model="foo.bar.content" text-model="foo.bar"></editor>
</div>
Template Controller
angular.module('foo').controller('fooController', ['$scope', ... , function ($scope, ...) {
$scope.foo = {}
$scope.foo.bar = {}
$scope.foo.bar.content = 'starting content'
$scope.$watch('foo.bar', function () {
console.log('content changed')
}, true)
}
The template two-way binds on its scope object $scope.foo.bar with the editor directive. When the text is changed, the editor's 'text-change' handler is fired and a property on the bound object is changed.
Editor Directive
angular.module('foo').directive('editor'), function (
restrict: 'E',
templateUrl: 'path/to/editor.html',
require: 'ng-model',
scope: {
textModel: '='
},
controller: [
...
$scope.editor = 'something that manages the text'
...
],
link: function (scope, ...) {
scope.editor.on('text-change', function () {
$scope.textModel.content = scope.editor.getText()
// forces parent to update. It only triggers the $watch once without this
// scope.$parent.$apply()
}
}
However, changing this property in the directive seems not to be hitting the deep $watch I've set on foo.bar. After some digging, I was able to use the directive's parent reference to force a $digest cycle scope.$parent.$apply(). I really shouldn't need to though, since the property is shared and should trigger automatically. Why does it not trigger automatically?
Here are some good readings that I've encountered that are pertinent:
$watch an object
https://www.sitepoint.com/understanding-angulars-apply-digest/
The on function is a jqLite/jQuery function. It will not trigger digest cycle. It is basically outside the angular's conscious. You need to manually trigger digest cycle using $apply.

Angular 1.5 component attribute presence

I'm refactoring some angular directives to angular 1.5-style components.
Some of my directives have behavior that depends on a certain attribute being present, so without the attribute having a specific boolean value. With my directives, I accomplish this using the link function:
link: function(scope,elem,attrs, controller){
controller.sortable = attrs.hasOwnProperty('sortable');
},
How would I do this with the angular 1.5-style component syntax?
One thing I could do is add a binding, but then I'd need to specify the boolean value. I'd like to keep my templates as-is.
Use bindings instead of the direct reference to the DOM attribute:
angular.module('example').component('exampleComponent', {
bindings: {
sortable: '<'
},
controller: function() {
var vm = this;
var isSortable = vm.sortable;
},
templateUrl: 'your-template.html'
});
Template:
<example-component sortable="true"></example-component>
Using a one-way-binding (indicated by the '<') the value of the variable 'sortable' on the controller instance (named vm for view model here) will be a boolean true if set as shown in the example. If your sortable attribute currently contains a string in your template an '#' binding may be a suitable choice as well. The value of vm.sortable would be a string (or undefined if the attribute is not defined on the component markup) in that case as well.
Checking for the mere presence of the sortable attribute works like this:
bindings: { sortable: '#' }
// within the controller:
var isSortable = vm.sortable !== undefined;
Using bindings may work but not if you are trying to check for the existence of an attribute without value. If you don't care about the value you can just check for it's existence injecting the $element on the controller.
angular
.module('yourModule')
.component('yourComponent', {
templateUrl: 'your-component.component.html',
controller: yourComponentsController
});
function yourComponentController($element) {
var sortable = $element[0].hasAttribute("sortable");
}
There is a built-in way to do this by injecting $attrs into the controller.
JS
function MyComponentController($attrs) {
this.$onInit = function $onInit() {
this.sortable = !!$attrs.$attr.hasOwnProperty("sortable");
}
}
angular
.module("myApp", [])
.component("myComponent", {
controller: [
"$attrs",
MyComponentController
],
template: "Sortable is {{ ::$ctrl.sortable }}"
});
HTML
<my-component sortable>
</my-component>
<my-component>
</my-component>
Example
JSFiddle

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
}
};
})

AngularJS directive's controller executed after page controller

I have directive with its own controller (or link function). Directive used inside template. Template declared like this:
when('/dashboard', {
templateUrl: 'views/dashboard/dashboard.html',
controller: 'DashboardCtrl',
}).
The problem is that Directive's controller executed after DashboardCtrl, because I want to use values which are set by directive. How I can cause directive's controller to be executed first (before DashboardCtrl)?
angular.module('directives')
.directive('timeRangePicker', ['timeService', function (timeService) {
return {
restrict: 'E',
scope: {
ngModel: '='
},
templateUrl: 'views/time-range-picker.html',
link: function ($scope, elem,
$scope.ngModel = {};
$scope.ngModel.dateFrom = today(); // initial value
$scope.ngModel.dateTo = tomorrow(); // initial value
}
};
}]);
Well, you technically CANT ( page controller will be executed before template evalutation )
And I wont lie to you, what you are trying to do seems to be for very bad reasons, but anyway, here how I would do it :
I would make my DashboardController wait (watch for) a property in the scope to be initialized by the directive controller. So this way, i would execute the function from the page controller after the executing of the directive controller.
But once again : why are you trying to do this ? A page root controller behaviour should not depends on it's content.
on parent controller :
$scope.state = { directiveInit : false }; // dont use direct primitive else the child directive wont update it on the parent scope
$scope.$watch("state.directiveInit", function(init) { if(init==true) { /* do the stuff*/}});
on the directive link fn or controller :
$scope.state.directiveInit = true;

Resources