Bind a property from one directive to another - javascript

I'm trying to use "Component approach" from Angular2 in Angular1, and my directives should send properties to each other. I've written code like this:
Parent directive
angular
.module('myModule')
.controller('parentController', ParentController)
.directive('parentDirective', () => {
return {
restrict: 'E',
scope: {},
controller: 'parentController',
controllerAs: 'ctrl',
templateUrl: 'parent-template.html'
}
});
class ParentController {
constructor() {
this._increasableValue = 0;
setInterval(() => {
this._increasableValue += 1;
})
}
get increasableValue() { return this._increasableValue; }
}
parent-template.html
<div class="container">
<child-directive inc="{{ctrl.increasableValue}}"></child-directive>
</div>
Child directive
angular
.module('myModule')
.controller('childController', ChildController)
.directive('childDirective', () => {
return {
restrict: 'E',
scope: {
increasableValue: '#inc'
},
bindToController: true,
controller: 'childController',
controllerAs: 'ctrl',
templateUrl: 'child-template.html'
}
});
class ChildController {
set increasableValue(value) { this._increasableValue = value; }
get increasableValue() { return this._increasableValue; }
}
child-template.html
<div class="container">
<div>{{ctrl.increasableValue}}</div>
</div>
But the increasableValue freezes on start value and do not changes at all. What should be done to get increasableValue from parent directive and bind it to child?
UPD:
Here is jsfiddle to demonstrate problem (look at the console too).

Yes you right! Problem is how you update your variable.
Angular not know, that value changed, and not call digest loop to update view.
Sample with $inteval service
angular.module('myModule', []);
var ParentController = (() => {
function ParentController($interval) {
this._increasableValue = 1;
var interval = $interval(() => {
this._increasableValue += 1;
console.log(this._increasableValue);
if (this._increasableValue > 20) {
$interval.cancel(interval);
}
}, 1000);
}
Object.defineProperty(ParentController.prototype, "increasableValue", {
get: function () { return this._increasableValue; },
enumerable: true,
configurable: true
});
return ParentController;
})()
var ChildController = (() => {
function ChildController() {}
Object.defineProperty(ChildController.prototype, "increasableValue", {
get: function () { return this._increasableValue; },
set: function (value) { this._increasableValue = value; },
enumerable: true,
configurable: true
});
return ChildController;
})();
angular
.module('myModule')
.controller('parentController', ParentController)
.directive('parentDirective', () => {
return {
restrict: 'E',
scope: {},
controller: 'parentController',
controllerAs: 'ctrl',
template: `
<div class="container">
<child-directive inc="ctrl.increasableValue"></child-directive>
</div>`
}
});
angular
.module('myModule')
.controller('childController', ChildController)
.directive('childDirective', () => {
return {
restrict: 'E',
scope: {
increasableValue: '=inc'
},
bindToController: true,
controller: 'childController',
controllerAs: 'ctrl',
template: `
<div class="container">
<div>{{ctrl.increasableValue}}</div>
</div>`
}
});
angular.bootstrap(document.getElementById('wrapper'), ['myModule'])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div id="wrapper">
<parent-directive>Loading...</parent-directive>
</div>
UPDATE: Or you can simple call $digest function like this
angular.module('myModule', []);
var ParentController = (() => {
function ParentController($scope) {
this._increasableValue = 1;
var interval = setInterval(() => {
this._increasableValue += 1;
console.log(this._increasableValue);
$scope.$digest();
if (this._increasableValue > 5) {
clearInterval(interval);
}
}, 1000);
}
Object.defineProperty(ParentController.prototype, "increasableValue", {
get: function () { return this._increasableValue; },
enumerable: true,
configurable: true
});
return ParentController;
})()
var ChildController = (() => {
function ChildController() {}
Object.defineProperty(ChildController.prototype, "increasableValue", {
get: function () { return this._increasableValue; },
set: function (value) { this._increasableValue = value; },
enumerable: true,
configurable: true
});
return ChildController;
})();
angular
.module('myModule')
.controller('parentController', ParentController)
.directive('parentDirective', () => {
return {
restrict: 'E',
scope: {},
controller: 'parentController',
controllerAs: 'ctrl',
template: `
<div class="container">
<child-directive inc="ctrl.increasableValue"></child-directive>
</div>`
}
});
angular
.module('myModule')
.controller('childController', ChildController)
.directive('childDirective', () => {
return {
restrict: 'E',
scope: {
increasableValue: '=inc'
},
bindToController: true,
controller: 'childController',
controllerAs: 'ctrl',
template: `
<div class="container">
<div>{{ctrl.increasableValue}}</div>
</div>`
}
});
angular.bootstrap(document.getElementById('wrapper'), ['myModule'])
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<div id="wrapper">
<parent-directive>Loading...</parent-directive>
</div>

It seems that the dragons were in setInterval approach. Changing it to a button with ng-click attribute solved my problem. Here is updated jsfiddle.
I will be grateful if someone explain the root of the problem.

Related

Controller and binding async variables

I want to control the display of elements using a variable that is defined in the controller.
But if variables change async, my code doesn't work as expected.
In my example, I output to console value of vm.fruit inside methods setFruit and hasFruit.
And after set value for vm.fruit in setFruit, in hasFruit vm.fruit value is undefined.
Have ideas about how to fix it?
And I don't want call controllers method inside directive.
UPD. I removed the definition exampleController from asyncChoice, how suggested #LeroyStav. I think he is right. But this not solved the problem.
angular.module('app', [])
.controller('exampleController', exampleController)
.directive('wrapper', wrapper)
.directive('asyncChoice', asyncChoice);
function exampleController() {
var vm = this;
vm.selectMode = false;
vm.fruit = undefined;
vm.hasFruit = hasFruit;
vm.selectFruit = selectFruit;
vm.setFruit = setFruit;
function hasFruit() {
console.log('hasFruit: ' + vm.fruit);
return (typeof vm.fruit !== 'undefined');
}
function selectFruit() {
vm.selectMode = true;
}
function setFruit(fruit) {
setTimeout(
function() {
vm.fruit = fruit;
vm.selectMode = false;
console.log(vm.fruit);
},
1000
);
}
}
function wrapper() {
return {
restrict: 'E',
trancslude: true,
controller: 'exampleController',
controllerAs: 'vm'
};
}
function asyncChoice() {
return {
template: `
<button ng-click="selectFruit({fruit: '🍊'})">🍊</button>
<button ng-click="selectFruit({fruit: '🍋'})">🍋</button>
`,
scope: {
selectFruit: '&'
},
controller: 'exampleController',
controllerAs: 'vm'
}
}
angular.bootstrap(
document.getElementById('root'), ['app']
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div id="root">
<wrapper>
<button ng-if="!vm.hasFruit()" ng-click="vm.selectFruit()">Choice fruit</button>
<p ng-if="vm.hasFruit()">You choice <span ng-bind="vm.fruit"></span></p>
<async-choice ng-if="vm.selectMode" select-fruit="vm.setFruit(fruit)"></async-choice>
</wrapper>
</div>
I provide $scope to exampleController and call $scope.$digest() after execution async operation.
angular.module('app', [])
.controller('exampleController', exampleController)
.directive('wrapper', wrapper)
.directive('asyncChoice', asyncChoice);
exampleController.$inject = ['$scope'];
function exampleController($scope) {
var vm = this;
vm.selectMode = false;
vm.fruit = undefined;
vm.hasFruit = hasFruit;
vm.selectFruit = selectFruit;
vm.setFruit = setFruit;
function hasFruit() {
console.log('hasFruit: ' + vm.fruit);
return (typeof vm.fruit !== 'undefined');
}
function selectFruit() {
vm.selectMode = true;
}
function setFruit(fruit) {
setTimeout(
function() {
vm.fruit = fruit;
vm.selectMode = false;
console.log(vm.fruit);
$scope.$digest();
},
1000
);
}
}
function wrapper() {
return {
restrict: 'E',
trancslude: true,
controller: 'exampleController',
controllerAs: 'vm'
};
}
function asyncChoice() {
return {
template: `
<button ng-click="selectFruit({fruit: '🍊'})">🍊</button>
<button ng-click="selectFruit({fruit: '🍋'})">🍋</button>
`,
scope: {
selectFruit: '&'
},
controller: 'exampleController',
controllerAs: 'vm'
}
}
angular.bootstrap(
document.getElementById('root'), ['app']
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div id="root">
<wrapper>
<button ng-if="!vm.hasFruit()" ng-click="vm.selectFruit()">Choice fruit</button>
<p ng-if="vm.hasFruit()">You choice <span ng-bind="vm.fruit"></span></p>
<async-choice ng-if="vm.selectMode" select-fruit="vm.setFruit(fruit)"></async-choice>
</wrapper>
</div>

Value of an angular directive is being set twice

I'm writing an Angular application that implements component approach (a-la Angular2-style). But I faced with a strange problem: when directive have to be set once, it somehow is being set twice. For a numbers in example it does not causes troubles, but for two-way binding with object it does.
There is a code:
Module init
angular.module('myModule', []);
Parent directive
var ParentController = (() => {
function ParentController() {
this._increasableValue = 1;
}
ParentController.prototype.update = function() {
this._increasableValue += 1;
}
Object.defineProperty(ParentController.prototype, "increasableValue", {
get: function() {
return this._increasableValue;
},
enumerable: true,
configurable: true
});
return ParentController;
})()
angular
.module('myModule')
.controller('parentController', ParentController)
.directive('parentDirective', () => {
return {
restrict: 'E',
scope: {},
controller: 'parentController',
controllerAs: 'ctrl',
template: `
<div class="container">
<child-directive inc="{{ctrl.increasableValue}}"></child-directive>
</div>
<button ng-click="ctrl.update()">Update</button>`
}
});
Child directive
var ChildController = (() => {
function ChildController() {}
Object.defineProperty(ChildController.prototype, "increasableValue", {
get: function() {
return this._increasableValue;
},
set: function(value) {
this._increasableValue = value;
console.log(this._increasableValue); // prints twice
},
enumerable: true,
configurable: true
});
return ChildController;
})();
angular
.module('myModule')
.controller('childController', ChildController)
.directive('childDirective', () => {
return {
restrict: 'E',
scope: {
increasableValue: '#inc'
},
bindToController: true,
controller: 'childController',
controllerAs: 'ctrl',
template: `
<div class="container">
<div>{{ctrl.increasableValue}}</div>
</div>`
}
});
bootstrapping
angular.bootstrap(document.getElementById('wrapper'), ['myModule'])
index.html (part)
<div id="wrapper">
<parent-directive>Loading...</parent-directive>
</div>
Why does it happen and what I can do to remove the second call?
Working example: jsfiddle.

How to call another directive from directive angularjs

I want to call alertForm directive in loginForm directive. Where I want call 'alertForm' directive in 'loginForm' is highlighted as //i want to call here
alertForm directive
angular.module('myApp')
.directive('alertForm', function () {
return {
templateUrl: 'app/directives/alert/alertForm.html',
restrict: 'E',
scope: {
topic: '=topic',
description: '=description'
},
controller: function($scope) {
$scope.words = [];
this.showAlert = function() {
$scope.description.push("hello");
};
}
};
});
loginForm directive
angular.module('myApp')
.directive('loginForm', function() {
return {
templateUrl: 'app/directives/loginForm/loginForm.html',
restrict: 'E',
scope: {
successCallback: '&',
errorCallback: '&',
emailField: '='
},
link: function (scope, element, attrs) {
},
controller: function ($rootScope, $scope, authenticationService) {
$scope.loginFormData = {};
$scope.inProgress = false;
$scope.onLogin = function (form) {
if (form.$valid) {
$scope.inProgress = true;
authenticationService.loginUser('password', $scope.loginFormData).then(function () {
$scope.successCallback({formData: $scope.loginFormData});
}, function (err) {
$scope.inProgress = false;
if (err.message) {
**// i want to call here**
}
});
}
}
}
};
});
You can use require config of directive.
When a directive requires a controller, it receives that controller as
the fourth argument of its link function. Ref : Documentation
You can implement this in your code
angular.module(‘myApp')
.directive('loginForm', function() {
return {
templateUrl: 'app/directives/loginForm/loginForm.html',
restrict: 'E',
require:'alertForm',
scope: {
successCallback: '&',
errorCallback: '&',
emailField: '='
},
link: function (scope, element, attrs, alertFormCtrl) {
scope.alertFormCtrl = alertFormCtrl;
},
controller: function ($rootScope, $scope, authenticationService) {
$scope.loginFormData = {};
$scope.inProgress = false;
$scope.onLogin = function (form) {
if (form.$valid) {
$scope.inProgress = true;
authenticationService.loginUser('password', $scope.loginFormData).then(function () {
$scope.successCallback({formData: $scope.loginFormData});
}, function (err) {
$scope.inProgress = false;
if (err.message) {
// Calling showAlert function of alertFormCtrl
$scope.alertFormCtrl.showAlert();
}
});
}
}
}
};
});
Add the following line in the app/directives/loginForm/loginForm.html :
<alertForm topic="something" description = "something" ng-if="showAlert"></alertForm>
Now inside the loginForm directive's controller : // i want to call here
use
$scope.showAlert = true;
Note: you can use some variable to setup the topic and description as well inside the alertForm.

How do I inherit properties from parent directive when using the controller as syntax?

I want to use the Controller As syntax in my Angular directives for two reasons. It's more plain JS and there's no dependency on the $scope service which will not be available in Angular 2.0.
It works great for a single directive but I cannot figure out how to print a property from the controller of a parent directive in a child directive.
function parentCtrl () {
this.greeting = { hello: 'world' };
}
function childCtrl () {}
angular.module('app', [])
.controller('parentCtrl', parentCtrl)
.controller('childCtrl', childCtrl)
.directive('myParent', function () {
return {
scope: {},
bindToController: true,
controller: 'parentCtrl',
controllerAs: 'parent',
template: '<my-child></my-child>'
}
})
.directive('myChild', function () {
return {
scope: {
greeting: '='
},
bindToController: true,
controller: 'childCtrl',
controllerAs: 'child',
template: '<p>{{ greeting.hello }}</p>'
}
});
You have to require the parent controller, the use the link function to inject the parent to the child. The myChild directive would become:
.directive('myChild', function () {
return {
scope: {
// greeting: '=' // NO NEED FOR THIS; USED FROM PARENT
},
bindToController: true, // UNNECESSARY HERE, THERE ARE NO SCOPE PROPS
controller: 'childCtrl',
controllerAs: 'child',
template: '<p>{{ child.greeting.hello }}</p>', // PREFIX WITH VALUE
// OF `controllerAs`
require: ['myChild', '^myParent'],
link: function(scope, elem, attrs, ctrls) {
var myChild = ctrls[0], myParent = ctrls[1];
myChild.greeting = myParent.greeting;
}
}
});
I found that you can use element attributes to pass properties from the parent directive controller's scope to a child.
function parentCtrl () {
this.greeting = 'Hello world!';
}
function myParentDirective () {
return {
scope: {},
controller: 'parentCtrl',
controllerAs: 'ctrl',
template: '<my-child greeting="ctrl.greeting"></my-child>'
}
}
function childCtrl () {}
function myChildDirective () {
return {
scope: {
greeting: '='
},
bindToController: true,
controller: 'childCtrl',
controllerAs: 'ctrl',
template: '<p>{{ ctrl.greeting }}</p><input ng-model="ctrl.greeting" />'
}
}
angular.module('parent', [])
.controller('parentCtrl', parentCtrl)
.directive('myParent', myParentDirective);
angular.module('child', [])
.controller('childCtrl', childCtrl)
.directive('myChild', myChildDirective);
angular.module('app', ['parent', 'child']);

Nested angular directives with typescript

Is it possible to use Typescript with nested angular directives?
http://jsfiddle.net/mrajcok/StXFK/
<div ng-controller="MyCtrl">
<div screen>
<div component>
<div widget>
<button ng-click="widgetIt()">Woo Hoo</button>
</div>
</div>
</div>
</div>
How would the following Javascript look as typescript code?
var myApp = angular.module('myApp',[])
.directive('screen', function() {
return {
scope: true,
controller: function() {
this.doSomethingScreeny = function() {
alert("screeny!");
}
}
}
})
.directive('component', function() {
return {
scope: true,
require: '^screen',
controller: function($scope) {
this.componentFunction = function() {
$scope.screenCtrl.doSomethingScreeny();
}
},
link: function(scope, element, attrs, screenCtrl) {
scope.screenCtrl = screenCtrl
}
}
})
.directive('widget', function() {
return {
scope: true,
require: "^component",
link: function(scope, element, attrs, componentCtrl) {
scope.widgetIt = function() {
componentCtrl.componentFunction();
};
}
}
})
//myApp.directive('myDirective', function() {});
//myApp.factory('myService', function() {});
function MyCtrl($scope) {
$scope.name = 'Superhero';
}
That code should work just as it is. However as a better practice you could use TypeScript classes for controllers if they become too large http://www.youtube.com/watch?v=WdtVn_8K17E&hd=1

Categories

Resources