Update directive scope when attribute changes using controllerAs syntax - javascript

What I need is to inject the data inside a directive dependent on the value passed to this directive via HTML element attribute value, which changes dynamically.
Here's my code:
angular.module('test', [])
.controller('ctrl', function (dataService) {
var vm1 = this;
vm1.data = dataService;
vm1.change = function () {
vm1.data.testValue = !vm1.data.testValue;
}
})
.directive('myDrct', function () {
return{
restrict: 'E',
controller: 'drctCtrl',
controllerAs: 'vm',
scope:{
passedValue: '#pass'
},
template: 'Actual value in directive: {{vm.passedValue}}'
}
})
.controller('drctCtrl', function ($scope) {
var vm = this;
vm.passedValue = $scope.passedValue;
$scope.$watch('watcher', function () {
vm.passedValue = $scope.passedValue;
})
})
.factory('dataService', function () {
return{
testValue: true
}
});
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl as vm">
Value passed as a parameter to directive: {{vm.data.testValue}}
<button ng-click="vm.change()">change</button>
<div>
<my-drct pass="{{vm.data.testValue}}"></my-drct>
</div>
</div>
And here is a demo plunker:
http://plnkr.co/edit/aAqKaD4G7QdwBYCLWAvM?p=preview
As suggested in many similar topics on StackOverflow, I've tried $scope.$watch, but I must be doing something wrong. The value gets inserted to the directive once and it doesn't get updated when the attribute value changes.
What shoud I change in the watcher function to get it to work? Or maybe this approach is not good at all in my case and I should try something else?
EDIT:
I must use "controllerAs vm" syntax, since this code is just a part of a bigger app already dependent on it.

The only thing you needs to change in your code in controller: vm.scope = $scope; instead of vm.passedValue = $scope.passedValue; and in a template change {{vm.passedValue}} to {{vm.scope.passedValue}}
Plunker: http://plnkr.co/edit/q8JBFu4FD5ECPFHYlPg6?p=preview
angular.module('test', [])
.controller('ctrl', function (dataService) {
var vm1 = this;
vm1.data = dataService;
vm1.change = function () {
vm1.data.testValue = !vm1.data.testValue;
}
})
.directive('myDrct', function () {
return{
restrict: 'E',
controller: 'drctCtrl',
controllerAs: 'vm',
scope:{
passedValue: '#pass'
},
template: 'Actual value in directive: {{vm.scope.passedValue}}'
}
})
.controller('drctCtrl', function ($scope) {
var vm = this;
vm.scope = $scope;
})
.factory('dataService', function () {
return{
testValue: true
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="ctrl as vm">
Value passed as a parameter to directive: {{vm.data.testValue}}
<button ng-click="vm.change()">change</button>
<div>
<my-drct pass="{{vm.data.testValue}}"></my-drct>
</div>
</div>

Related

Call directive controller methods from outside

I write a directive that provide a tab functionality, I use latest version of AngularJS (v1).
In my directive I have a controller that expose an api to a children directives, the scope is isolated in all directives:
Parent Directive
scope: {},
controller: function($scope) {
$scope.values = [];
this.addValue = function(value) {
$scope.values.push(value);
}
}
Child Directive on link function
scope: {},
transclude: true,
template: '<div ng-transclude></div>',
replace: true,
link: function(scope, element, attr, parentCtrl)
{
parentCtrl.addValues('test')
}
In child directive I have a custom html with own controller:
TestCtrl
function TestCtrl($scope){
var vm = this;
... other logic
}
Implementation
<parent>
<child>
<div ng-controller="TestCtrl as t">
<button ng-click="addValues('new_test')"></button>
</div>
</child>
</parent>
I need to call "addValues" method (inside directive controller) on click on my button.
How to organise the code to make this?
var module = angular.module('app', []);
module.controller('root', function ($scope) {
$scope.test = function () {
console.log('i am clicked');
}
});
module.component('child', {
template: '<button type="button" data-ng-click="$ctrl.click()">Click me</button>',
controller: myController,
bindings: {
onClick: '&'
}
});
function myController() {
var ctrl = this;
ctrl.click = function () {
this.onClick();
}
}
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js"></script>
</head>
<body data-ng-app="app">
<div data-ng-controller="root">
<child data-on-click="test()"></child>
</div>
</body>
</html>
It is just an example
Here you can read more.
Hope this helps.
MAy be it will be usefull to read about angular's best practice here

Why won't $compile work with service using a directive?

Please have a look at this example, since it is the best way to explain the problem.
In this example if you click the directive link, it does not compile the template, but instead displays it as "{{1+1}}".
On the other hand if you click the "Simple link" it compiles the template and displays "2" instead.
angular.module('myApp', [])
.provider('$popup', function() {
var service = {};
this.$get = ['$compile', '$rootScope', function($compile, $rootScope) {
var template = $('<div>{{1+1}}</div>');
service.go = function() {
$(document.body).append(template);
$compile(template)($rootScope);
}
return service;
}];
})
.directive('popupLink', ['$popup', function($popup) {
return {
restrict: 'A',
scope: {},
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
return false;
});
}
};
}])
.controller('mainCtrl', ['$scope', '$popup', function($scope, $popup) {
$scope.go = function() {
$popup.go();
};
}])
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<a ng-href="/test" popup-link>Directive link</a>
Simple link
</div>
My question is why isn't the template compiling with the directive? (but it does in the controller)
And how do I fix it so that it compiles in the directive also?
P.S. Here is the jsbin link in case you want to play around with the code:
http://jsbin.com/vuzutipedu/edit?html,js,output
The directive needs to do scope.$apply():
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
//ADD apply
scope.$apply();
return false;
});
The click event handler executes outside the AngularJS framework. A framework digest cycle needs to be performed to execute the watcher for the {{1+1}} interpolation.
It works with the ng-click directive because that directive includes scope.$apply.
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
DEMO
angular.module('myApp', [])
.provider('$popup', function() {
var service = {};
this.$get = ['$compile', '$rootScope', function($compile, $rootScope) {
var template = $('<div>{{1+1}}</div>');
service.go = function() {
$(document.body).append(template);
$compile(template)($rootScope);
}
return service;
}];
})
.directive('popupLink', ['$popup', function($popup) {
return {
restrict: 'A',
scope: {},
link: function link(scope, element, attrs) {
element.click(function() {
$popup.go();
//ADD apply
scope.$apply();
return false;
});
}
};
}])
.controller('mainCtrl', ['$scope', '$popup', function($scope, $popup) {
$scope.go = function() {
$popup.go();
};
}])
<script src="//unpkg.com/jquery"></script>
<script src="//unpkg.com/angular/angular.js"></script>
<div ng-app="myApp" ng-controller="mainCtrl">
<a ng-href="/test" popup-link>Directive link</a>
Simple link
</div>
Try this in $get, instead of $compile(template)($rootScope)
$compile(angular.element(template))(angular.element(template).scope());
Let me know if it works

Defining my own 'require' on a directive, but throw error - AngularJs

Let us say I have this html:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name="name">Hello, {{name}}!</my-directive>
</div>
with this simple controller:
myApp.controller('MyCtrl', function ($scope) {
$scope.name = 'Superhero';
});
And I have a directive in which I want to change the 'name' using require like this:
myApp.directive('myDirective', function($timeout) {
var controller = ['$scope', function ($scope) {
$scope.name = "Steve";
}];
return {
restrict: 'EA',
require: 'myName',
controller: controller,
link: function(scope, element, attrs, TheCtrl) {
TheCtrl.$render = function() {
$timeout(function() {
TheCtrl.$setViewValue('StackOverflow');
}, 2000);
};
}
};
});
But throws:
Error: No controller: myName
Here is the fiddle
But if I implement it using ng-model, works. Look here in this other fiddle
I have read that if you use 'require' in a directive, you need to have a controller for it.
So:
What I'm doing is wrong? It is not in this way? I need to do any other thing?
Well finally I got it.
Essencially what I'm trying to do is something called: 'Communication between directives using controllers'. I have found an article explaining this, and helped me a lot:
The view:
<div ng-controller="MyCtrl">
<br>
<my-directive my-name>Hello, {{name}}!</my-directive>
</div>
As you see above, there are two directives: my-directive and my-name. I will call inside my-directive a function from the controller of my-name directive using require.
myDirective:
myApp.directive('myDirective', function($timeout) {
return {
require: 'myName',
link: function(scope, element, attrs, myNameCtrl) {
$timeout(function() {
myNameCtrl.setName("Steve");
}, 9000);
} // End of link
}; // return
});
myName:
myApp.directive('myName', function($timeout) {
var controller = ['$scope', function ($scope) {
// As I tried, this function can be only accessed from 'link' inside this directive
$scope.setName = function(name) {
$scope.name = name;
console.log("Inside $scope.setName defined in the directive myName");
};
// As I tried, this function can accessed from inside/outside of this directive
this.setName = function(name) {
$scope.name = name;
console.log("Inside this.setName defined in the directive myName");
};
}];
return {
controller: controller,
link: function(scope, element, attrs, localCtrl) {
$timeout(function() {
localCtrl.setName("Charles");
}, 3000);
$timeout(function() {
scope.setName("David");
}, 6000);
} // End of link function
};
});
Interesting and works like a charm. Here is the fiddle if you want to try it out.
Also, you can get communication between directives using events. Read this answer here on SO.

AngularJS ng-controller directive does not accept variable (scope function) from javascript, does not give any error either

I am relatively new to angularJS, I am trying to set up a page where inturn multiple pages are called depending upon the selection made previously.
All the pages have their own controller, so I am trying to set the controller and view src through the javascript and using them in HTML tags.
Following is what I am doing:
HTML page:
<div ng-if="sidebarName=='sidebar-device-wire'">
<div ng-controller="getSidebarCtlr">
<div ng-include src="sidebarSrc"></div>
</div>
</div>
javascript:
$scope.sidebarSrc="views/sidebars/sidebar-device.html";
$scope.sidebarCtlr="SidebarDeviceCtrl";
$scope.getSidebarCtlr = function(){return $scope.sidebarCtlr;}
For some reason though, this does not work. i can get the HTML page but the controller is not being called. Can anyone please tell me what I am doing wrong?
I would also recommend to use ngRoute or ui.router because there are many features that aren't easy to implement from scratch (like named views, nested views / nested states or resolves) and these modules are well tested.
Not sure why your controller isn't running but I guess that the expression of the controller is evaluated before your controller that is setting the name is running. So it will be always undefined at compile time.
But if you really like to implement a very basic router you could do it like in the following demo (or in this fiddle).
Update 21.12.2015
Here are some router examples that I wrote for other SO questions:
simple ui.router example - jsfiddle
more complex nested state example ui.router - jsfiddle
dynamic link list with ngRoute - jsfiddle
Please also have a look at ui.router github pages to learn more about it.
angular.module('simpleRouter', [])
.directive('simpleView', simpleViewDirective)
.provider('simpleRoutes', SimpleRoutesProvider)
.controller('MainController', MainController)
.controller('HomeController', HomeController)
.config(function(simpleRoutesProvider) {
simpleRoutesProvider.state([{
url: '/',
templateUrl: 'home.html',
controller: 'HomeController'
}, {
url: '/view1',
templateUrl: 'view1.html'
}, {
url: '/view2',
templateUrl: 'view2.html',
controller: function($scope) {
$scope.test = 'hello from controller'
}
}]);
simpleRoutesProvider.otherwise('/');
})
function HomeController($scope) {
$scope.hello = 'hello from home controller!!';
console.log('home controller started')
}
function MainController($scope) {
$scope.hello = 'Main controller test';
}
function simpleViewDirective() {
return {
restrict: 'EA',
scope: {},
template: '<div ng-include="templateUrl"></div>',
controller: function($scope, $location, $controller, simpleRoutes) {
var childControllerInst;
$scope.templateUrl = simpleRoutes.currentRoute.templateUrl || simpleRoutes.otherwise.templateUrl;
$scope.$watch(function() {
return $location.path();
}, function(newUrl) {
//console.log(newUrl)
$scope.templateUrl = simpleRoutes.changeRoute(newUrl);
childControllerInst = $controller(simpleRoutes.currentRoute.controller || function() {}, {$scope: $scope});
});
$scope.$on('$destroy', function() {
childControllerInst = undefined;
})
},
link: function(scope, element, attrs) {
}
}
}
function SimpleRoutesProvider() {
var router = {
currentRoute: {
templateUrl: ''
},
states: [],
otherwise: {},
changeRoute: function(url) {
var found = false;
angular.forEach(router.states, function(state) {
//console.log('state', state);
if (state.url == url) {
router.currentRoute = state;
found = true;
}
});
if (!found) router.currentRoute = router.otherwise;
//console.log(router.currentRoute);
return router.currentRoute.templateUrl;
}
};
this.state = function(stateObj) {
router.states = stateObj;
};
this.otherwise = function(route) {
angular.forEach(router.states, function(state) {
if (route === state.url ) {
router.otherwise = state;
}
});
//console.log(router.otherwise);
};
this.$get = function simpleRoutesFactory() {
return router;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="simpleRouter" ng-controller="MainController">
<script type="text/ng-template" id="home.html">home route {{hello}}</script>
<script type="text/ng-template" id="view1.html">view1</script>
<script type="text/ng-template" id="view2.html">view2 {{test}}</script>
<div simple-view="">
</div>
home
view1
view2
<br/>
{{hello}}
</div>
What's that code means? $scope.getSidebarCtlr = function(){return $scope.sidebarCtlr;}
the ng-directive requires a Controller name, its argument type is string and you cannot pass a simple function, you need to register a valid controller associating it to a module via the controller recipe.
https://docs.angularjs.org/guide/controller
angular.module('test', []).controller('TestCtrl', function($scope) {
$scope.greetings = "Hello World";
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<section ng-app="test">
<article ng-controller="TestCtrl">{{ greetings }}</article>
</section>

Bind a service variable to a directive?

I have a controller which contains a function that gets some data from the server. I store that data in a service variable. This service is then injected into a directive. I want the directive to be auto updated, whenever this function is called and the data is renewed.
My controller:
angular
.module('myApp')
.controller('myCtrl', ['$scope', 'SomeService', function($scope, SomeService) {
$scope.update = function() {
SomeService.myValue = 100;
}
}]);
The directive:
angular.module('myApp')
.directive('myDirective', ['SomeService', function(SomeService) {
return {
templateUrl : 'views/myDirective.html',
restrict : 'E',
scope : false,
controller : function($scope) {
this.myValue = SomeService.myValue;
}
};
}]);
The template:
<div>
{{ myValue }}
</div>
The update function is called when a button is clicked and it updates myValue to a new value. I want it to be automatically reflected in the directive.
Plunk: http://plnkr.co/edit/OUPzT4MFS32OenRIO9q4?p=preview
Please see working demo below
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope, SomeService) {
$scope.name = SomeService.data;
$scope.update = function() {
$scope.name.myValue += 1;
}
});
app.factory('SomeService', function() {
var data = {
myValue: 0
};
return {
data: data
}
});
app.directive('myDirective', ['SomeService',
function(SomeService) {
return {
templateUrl: 'myDirective.html',
restrict: 'EA',
scope: false,
link: function(scope, elem, attr) {
scope.data = SomeService.data
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="MainCtrl">
<p>My Value: {{name.myValue}}</p>
<button ng-click="update()">Click</button>
<hr/>
<div my-directive></div>
<script type="text/ng-template" id="myDirective.html">
<h3>My Directive</h3>
<p>Value: {{data.myValue}}</p>
</script>
</div>
</div>
You can try by adding the reference of the service to the directive itself..
The directive:
angular.module('myApp')
.directive('myDirective', ['SomeService', function(SomeService) {
return {
templateUrl : 'views/myDirective.html',
restrict : 'E',
scope : false,
controller : function($scope) {
this.SomeService = SomeService;
}
};
}]);
The template:
<div>
{{ SomeService.myValue }}
</div>
Edit : I went through your plunker, and have finally got it working.
You can check the updated code here
#RutwickGangurde and others who were having issues, if you're trying to set a scope variable that is not an object it won't work. I'm guessing that's what you're currently doing in your service:
...
this.myVar = true;
...
and then trying to set it in the directive/controller:
...
scope.myVar = myService.myVar;
...
That will NOT work for getting the updated variable in the service when it changes.
Try this instead in your service:
...
this.myObj = {};
this.myObj.myVar = true;
...
and in your directive/controller:
...
scope.myValue = myService.myObj;
...
and in your html:
...
{{ myValue.myVar }}
...
I would have made this as a comment, but I don't have sufficient privileges yet so decided to post as a response with a very brief example.

Categories

Resources