angular 1.5 component binding not updated from certain events - angularjs

I have an angular 1.5 component (i.e. myCarousel: it wraps a bootstrap carousel) that triggers an event and exposes it with a function binding.
A parent container (i.e. myContainer) component binds a function to that event and modifies some inner state when triggered.
Another child-component of the parent container is bound to the container's inner state.
The problem:
When I trigger the event on myCarousel from a button click event, everything works fine and I can see the child-component's binding being updated.
However, when I trigger the event from a carousel slide event (slide.bs.carousel), the binding does not get updated.
Here's a plunker to demonstrate:
https://plnkr.co/edit/AHxaX8o0sE94Nfir7DVB
Can anyone explain why this is happening and how to solve this?
Some relevant code:
mainApp.component("myCarousel", {
templateUrl: "myCarousel.html",
bindings: {
onEventTriggered: "&"
},
controllerAs: "vm",
controller: function() {
let vm = this;
vm.$onInit = function() {
console.log("init!");
$("#theCarousel").carousel();
$("#theCarousel").on("slide.bs.carousel", (event) => {
console.log("sliding " + event.direction);
vm.onEventTriggered();
});
};
}
});
mainApp.component("myContainer", {
templateUrl: "myContainer.html",
controllerAs: "vm",
controller: function() {
let vm = this;
vm.counter = 0;
vm.triggerEvent = function() {
console.log("event!");
vm.counter++;
}
}
});
mainApp.component("childComponent", {
template: "<div>Event {{vm.attribute}}</div>",
controllerAs: "vm",
bindings: {
attribute: "<"
}
});

One way to do this is :
controller: function($scope) {
let vm = this;
vm.$onInit = function() {
console.log("init!");
$("#theCarousel").carousel();
$("#theCarousel").on("slide.bs.carousel", (event) => {
vm.onEventTriggered();
console.log("sliding " + event.direction);
$scope.$apply();
});
};
}
using $scope.$apply()
updated plunkr : https://plnkr.co/edit/8yZOyuQfofdoYz00tFBk?p=preview
You need to use $scope.$apply(), because $("#theCarousel").on("slide.bs.carousel", ... is jquery code, and you need to notify angular about it using $scope.$apply()

Related

Can't add an attribute to an element using $postLink - AngularJS

I have a simple component and what I'm trying to do is to add to my input element an attribute multiple dynamically but somewhy it doesn't work. Why? And is there any way to do what I want?
app.component('myComponent', {
templateUrl: 'tmpl.html',
bindings: {
str: '#'
},
controller: function () {
var ctrl = this;
ctrl.$postLink = function () {
$('#myInputId').attr('multiple', '');
}
}
}
This is the solution:
app.component('myComponent', {
templateUrl: 'tmpl.html',
bindings: {
str: '#'
},
controller: function ($element) {
var ctrl = this;
ctrl.$postLink = function () {
$element.find('input').attr('multiple', 'multiple');
}
}
}
The trick was in linking stuff. Code from the question doesn't work because the input not exists in DOM yet at the time $postLink hook triggered. So what we need is to inject $element service into controller and use it to manipulate DOM.

Angular js binding "<" give undefined inside controller

I have "invite-friends-code" component which get data through binding "<". The problem is, it is pass correctly and show in the view directly, but when i trying to munipulate with it inside "invite-friends-code" controller it gives me "undefined"
component
const inviteFriendsCodeComponent = {
bindings: {
leagueCode: '<'
},
templateUrl: '/js/common/invite-friends-code/invite-friends-code.html'+assetsVersion,
controller: 'InviteFriendsCodeController'
}
binding using inside other component view:
<invite-friends-code league-code="$ctrl.leagueData.code"></invite-friends-code>
controller:
ctrl.$onInit = () => {
console.log(ctrl.leagueCode) // gives me undefined here
}
Your bindings are not initialized yet!
You can use $onInit function.
app.component('yourComponent', {
bindings: {
leagueCode: '<'
},
controller: function() {
var ctrl = this;
this.$onInit = function() {
console.log(this.leagueCode)
});
}
}
Official doc

Is it pointless to use $onInit in this case

I have an angularJS (1.5+) component that has some one-way binding properties that are bound to a parent controller's variables. This component uses those properties in the bindings object directly and doesn't need to set up any local variables.
When the page loads, the component's bindings are initialized and bound to default values because the parent controller initializes its variables to default values.
example code:
App.component('myComponent',{
bindings:{
binding_1: '<',
binding_2: '<'
},
template:'<div ng-show="$ctrl.binding_1">' +
'<input type="button" ng-click="$ctrl.clicked()">' +
'</div>',
controller: function () {
var ctrl = this;
// would ctrl.$onInit = function(){...} be beneficial here at all?
ctrl.clicked = function(){
console.log("ctrl.binding_2.text");
}
});
If the component only uses its binding properties and those properties are initialized to default values via the parent controller variables upon page load, then what would be the benefit(s) of implementing $onInit and where would I expect to see this (these) benefit(s)?
$onInit is the component's initialization lifecycle hook. You should perform your initialization logic there. In the case of your code sample, the component bindings would likely be accessible since they are being accessed in a click handler. Here is a small demo demonstrating $onInit.
Component Initialization with $onInit
angular.module('app',[])
.controller("MyController", function(){
var ctrl = this;
ctrl.title = "Hello World";
})
.component("myComponent", {
bindings:{
bindingOne: '<'
},
template: ' <h1>{{ctrl.title}}</h1>',
controller: function(){
this.$onInit = function(){
this.title = this.bindingOne;
}
},
controllerAs: "ctrl"
})
Component Initialization Without $onInit
angular.module('app',[])
.controller("MyController", function(){
var ctrl = this;
ctrl.title = "Hello World";
})
.component("myComponent", {
bindings:{
bindingOne: '<'
},
template: ' <h1>{{ctrl.title}}</h1>',
controller: function(){
this.title = this.bindingOne;
},
controllerAs: "ctrl"
})

Two way data binding with directive doesn't work

I have a controller used to add tasks. On that page a user needs to select a group to act upon. I have written a directive that is used to allow a user to pick groups (folders)
My page controller
function AddTaskController($scope) {
var vm = this;
vm.group = { whatsit: true };
$scope.$watch("vm.group", function () {
console.log("controller watch", vm.group);
},true);
}
The page html where the directive is used
<em-group-selection group="vm.group"></em-group-selection>
The directive configuration
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
templateUrl: '/views/templates/common/folderselection.html'
};
}
The directive controller:
function GroupSelectionDirectiveController($scope) {
var vm = this;
$scope.$watch("group", function () { console.log("yo1", vm.group); }, true)
$scope.$watch("vm.group", function () { console.log("yo2", vm.group); }, true)
}
Now when this fires, both console.log() calls in the directive fire once, with undefined. They never fire again. If in the controller I set vm.group to something else the $watch in the AddTaskController never gets fired.
Why isnt the data binding working?
Update:
I notice that if, in the directive, I change the init() function in my directive to use $scope it works! Can I not, as Fedaykin suggests, use controllerAs with two way data binding?
function init() {
$timeout(function () {
$scope.group.shizzy = 'timeout hit';
}, 200);
}
Turns out that if you use isolate scopes and controlelrAs syntax you need to also use bindToController : true. Without this you will not be able to only use vm and will have to use $scope for the isolate scope variables
More information can be found in the John Papa style guide and this SO answer
The final directive setup is as so:
function GroupSelectionDirective() {
return {
scope: {
group: '='
},
controller: GroupSelectionDirectiveController,
controllerAs: 'vm',
bindToController: true,
templateUrl: '/views/templates/common/folderselection.html'
};
}

this reference in callback with controllerAs-Syntax

I'm working on a angularjs project and I'm trying to implement it as close as possible to angularjs 2.
In angularjs 2 there will be no more ng-controller and $scope, so I'm using directives instead of ng-controller. Also I'm trying to avoid the use of $scope by using the controllerAs-syntax.
My directives would look something like this:
angular.module('myApp').directive('myDirective', function() {
return {
controller: function() {
this.data = '123';
},
controllerAs: 'ctrl',
template: ' <p>{{ctrl.data}}</p> '
};
});
So far everything works fine. The problems start when I'm trying to call a methode or a property on the controller within a callback-function. In this case 'this' is not referencing the actual controller-instance any more.
Example:
angular.module('myApp').directive('myDirective', function($http) {
return {
controller: function() {
this.data = '';
this.loadData = function(){
$http.get("someurl.com")
.success(function(response) {
this.data = response.data; // --> this doesn't work
});
}
this.loadData();
},
controllerAs: 'ctrl',
template: ' <p>{{ctrl.data}}</p> '
};
});
Is there a way to get a reference to my controller-instance within the callback so I can use the controllers methods and properties within the callback?
What you need to do is store a reference to this in a variable at the beginning of the controller...then add your properties to that variable. Context won't be lost inside callbacks this way.
A common convention is to use var vm for "View Model" but naming of variable is subjective
angular.module('myApp').directive('myDirective', function($http) {
return {
controller: function() {
var vm = this;
vm.data = '';
vm.loadData = function(){
$http.get("someurl.com")
.success(function(response) {
vm.data = response.data;
});
}
vm.loadData();
},
controllerAs: 'ctrl',
template: ' <p>{{ctrl.data}}</p> '
};
});
A popular angular style guide

Resources