I'm using angular#1.6.3, bootstrap#3.3.7 and jquery#1.12.4.
I'm trying to wrap a <select> tag into bootstrap-multiselect jQuery plugin.
To do that, I'm trying to use custom directives with an isolated scope.
Here is my HTML:
<div ng-app="myApp" ng-controller="MainCtrl as vc">
<select multiple="multiple"
multiselect
ng-model="vc.selectedCountries"
data-options="vc.allCountries"
data-list-type="Countries">
<optgroup ng-repeat="(continent, countries) in options" label="{{continent}}">
<option ng-repeat="country in countries" value="{{country.code}}">
{{country.code}} - {{country.name}}
</option>
</optgroup>
</multiselect>
</div>
And here is my JS:
var app = angular.module('myApp', [])
app.controller('MainCtrl', ['$scope', '$http', function ($scope, $http) {
var self = this
self.allCountries = {}
$http.get('/countries.json')
.then(function (result) {
self.allCountries = result.data
})
}])
app.directive('multiselect', [function () {
return {
restrict: 'A',
scope: {
options: '=',
listType: '#',
},
transclude: true,
controller: ['$scope', '$element', '$attrs', '$timeout', function ($scope, $element, $attrs, $timeout) {
$scope.$watch('options', function(newVal, oldVal) {
console.log(newVal, oldVal)
$timeout(function () {
console.log($scope.options)
$($element).multiselect('rebuild')
}, 1)
})
$($element).multiselect()
}]
}
}])
However, this way the <select> is never populated.
This is not a problem with the plugin itself, because if I remove the lines containing .multiselect() calls, a regular multiselect appears empty.
I think it's something to do with transclusion and scope, because if I change transclude: true, to transclude: false, and
<optgroup ng-repeat="(continent, countries) in options" label="{{continent}}">
to
<optgroup ng-repeat="(continent, countries) in vc.allCountries" label="{{continent}}">
it works just fine.
However, I'm trying to generalize this component, so I don't want to depend on the controller.
You need not depend on your controller you have written in your code. You can try this alternative.
<!DOCTYPE html>
<html ng-app="exampleApp" >
<head>
<meta charset="utf-8">
<title>ng app</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js" type="text/javascript"></script>
<script>
var myApp = angular.module('exampleApp', []);
myApp.directive('highlight', function(){
return function (scope, element, attrs) {
console.log(scope.$root.list);
}
});
myApp.run(function($rootScope){
$rootScope.list = [1,2,3];
});
</script>
</head>
<body>
<highlight listdata="$parent.list"></highlight>
</body>
</html>
Related
I'm trying to implement the classic Country > State > City combobox using angular directives.
I'm willing to link them by a custom attribute pair name/depends-on, but I don't know if there's a better form.
My HTML is the following:
<div ng-app="app" ng-controller="Ctrl">
<multiselect source-url="/countries.json" auto-init="true" name="Contries">
<select multiple ng-model="$parent.selectedOptions" ng-change="$parent.handleChange()">
<optgroup ng-repeat="(continent, countries) in $parent.optionList" label="{{continent}}">
<option ng-repeat="country in countries" value="{{country.code}}">
{{country.code}} - {{country.name}}
</option>
</optgroup>
</select>
</multiselect>
<multiselect source-url="/states.json" depends-on="Countries" name="States">
<select multiple ng-model="$parent.selectedOptions" ng-change="$parent.handleChange()">
<optgroup ng-repeat="(continent, countries) in $parent.optionList" label="{{continent}}">
<option ng-repeat="country in countries" value="{{country.code}}"> {{country.code}} - {{country.name}}
</option>
</optgroup>
</select>
</multiselect>
</div>
The Javascript:
app.directive('multiselect', function () {
return {
restrict: 'E',
transclude: true,
scope: {
sourceUrl : '#',
autoInit : '#',
dependsOn : '#'
},
controller: ['$scope', '$element', '$attrs', '$transclude', '$http', function ($scope, $element, $attrs, $transclude, $http) {
$scope.handleChange = function handleChange() {
console.log($scope.selectedOptions)
}
console.log($scope.dependsOn)
function updateSource() {
const config = {
params : {}
}
if ($scope.dependsOn) {
// how do I get dependsOnValue?????
config.params[$scope.dependsOn.toLowerCase()] = $scope.dependsOnValue
}
$http.get($scope.sourceUrl, config)
.then(function (response) {
$scope.optionList = response.data
})
}
if ($scope.autoInit) {
updateSource()
}
}],
template: '<ng-transclude></ng-transclude>'
}
})
The question is: how do I watch for changes in Countries in order to get its value to update States?
I'm trying to make this as reusable as possible.
I've a strange behavior using multiple transclude component in Angularjs:
changes in first slot model no visible in controller.
<div ng-app="myApp" ng-controller="testController">
<script type="text/ng-template" id="component-template.html">
<div style="color:red;" ng-transclude="heading">
</div>
<div style="color:blue;" ng-transclude="body">
</div>
</script>
Example1
<input ng-model="example1Model"/>
<test-component>
<panel-heading>
Example2
<input ng-model="example2Model"/>
</panel-heading>
<panel-body>
Example1Result:{{example1Model}}<br/>
Example2Result:{{example2Model}}
</panel-body>
</test-component>
</div>
<script>
angular.module("myApp", [])
.controller("testController", function ($scope, $location) {
})
.component("testComponent", {
templateUrl: "component-template.html",
transclude: {
heading: "panelHeading",
body: "panelBody"
},
controller: function ($scope, $element, $attrs) {
this.$doCheck = function () {
//do anything
}
}
});
</script>
Now if you try to test it in this JSfiddle:https://jsfiddle.net/Lpveophe/1/
Why binding model example2Model did not working?
However example1Model binding model working correctly.
You need to use . in ng-model to make it work. For better understand of scope, please go through this article: https://github.com/angular/angular.js/wiki/Understanding-Scopes
To make it work, replace example2Model with o.example2Model everywhere and change your controller to:
.controller("testController", function ($scope, $location) {
$scope.o = {};
$scope.o.example2Model = '';
})
i am familiar with the syntax of controller & name but i'm trying to create a generic directive that will get a list of items and for each item i need to specify a controller.
This is my main directive:
function controlPanel() {
var directive = {
restrict: 'E',
replace: true,
scope: {
controlPanelItems: "=sbControlPanelItems"
},
templateUrl: 'control-panel.html',
link: link
};
return directive;
function link(scope, element) {
}
}
Here is the directive template:
<sb-control-panel-item ng-repeat="controlPanelItem in controlPanelItems"
sb-title="controlPanelItem.title"
sb-template-url="controlPanelItem.templateUrl"
sb-control-panel-item-controller="controlPanelItem.controller"></sb-control-panel-item>
My issue is with the sb-control-panel-item-controller attribute.
Angular throws exception when i'm passing variable, it work's great when i'm passing simple string (the name of the controller).
Here is the code of the control-panel-item directive:
function controlPanelItem() {
var directive = {
restrict: 'E',
replace: true,
scope: {
title: '=sbTitle',
templateUrl: '=sbTemplateUrl'
},
templateUrl: 'control_panel_item.html',
controller: '#',
name: 'sbControlPanelItemController',
link: link
};
return directive;
function link(scope, iElement, iAttributes, controller) {
}
}
Maybe there is a way to inject the controller through the link function and then i'll just pass it through the scope?
You can use the $controller service to instantiate whatever controller dynamically inside the directive, check this plunkr.
Just bear in mind that if you wanted to specify a controller statically now, you would need to enclose it in single quotes.
Basically the code would be like:
function MainCtrl() {
this.firstCtrl = 'FirstCtrl';
this.secondCtrl = 'SecondCtrl';
}
function FirstCtrl() {
this.name = 'First Controller';
}
function SecondCtrl() {
this.name = 'Second Controller';
}
function fooDirective() {
return {
scope: {
ctrl: '='
},
template: '<div>{{foo.name}}</div>',
controller: ['$controller', '$scope', function($controller, $scope) {
var foo = $controller($scope.ctrl, {$scope: $scope});
return foo;
}],
controllerAs: 'foo',
link: function ($scope, $element, $attrs, $ctrl) {
console.log($scope.ctrl);
}
};
}
angular
.module('app', [])
.directive('fooDirective', fooDirective)
.controller('MainCtrl', MainCtrl)
.controller('FirstCtrl', FirstCtrl)
.controller('SecondCtrl', SecondCtrl);
and this would be the HTML
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#1.5.8" data-semver="1.5.8" src="https://code.angularjs.org/1.5.8/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="MainCtrl as main">
<h1>
Test
</h1>
<foo-directive ctrl="main.firstCtrl">
"name: " {{foo.name}}
</foo-directive>
<foo-directive ctrl="main.secondCtrl">
{{foo.name}}
</foo-directive>
</body>
</html>
========================================================================
WRONG OLD ANSWER
From this blog entry seems to be an undocumented property that allows you to do exactly what you need.
function FirstCtrl() {
this.name = 'First Controller';
}
function SecondCtrl() {
this.name = 'Second Controller';
}
function fooDirective() {
return {
scope: {},
name: 'ctrl',
controller: '#',
controllerAs: 'foo',
template: '<div></div>',
link: function ($scope, $element, $attrs, $ctrl) {
}
};
}
angular
.module('app', [])
.directive('fooDirective', fooDirective)
.controller('FirstCtrl', FirstCtrl)
.controller('SecondCtrl', SecondCtrl);
So all you need to do in your directive is add a property name linked to the attribute you will use with the name of your controller.
<foo-directive ctrl="FirstCtrl"></foo-directive>
<foo-directive ctrl="SecondCtrl"></foo-directive>
If your directive, as per your question, needs to be from a property rather than a string, use {{}} notation:
<sb-control-panel-item ng-repeat="controlPanelItem in controlPanelItems"
sb-title="controlPanelItem.title"
sb-template-url="controlPanelItem.templateUrl"
sb-control-panel-item-controller="{{controlPanelItem.controller}}"></sb-control-panel-item>
I am creating a custom directive in a controller and calling it in ng-repeat as follows:
HTML:
<div ng-controller="TestCtrl">
<div ng-repeat="page in pages">
<custom
load-data="loadData = fn">
</custom>
</div>
</div>
JS:
In Test directive I am calling loadData as follows:
scope: {
loadData: "&"
}
controller: ['$scope', '$element', '$timeout', '$filter', function ($scope, $element, $timeout, $filter) {
$scope.loadData({ fn: function(data) {
//Data calc.
}});
}
I am calling loadData from TestCtrl as follows:
App.controller('TestCtrl', function($scope, $http, $timeout, $rootScope) {
$scope.loadData(data);
}
I need to call loadData function but it is throwing error as undefined is not a function
Is there any way I can access scope of child directive from outside it. I went through few SO questions where it was mentioned that using ng-repeat changes scope, but I was not able to figure out a solution to it. I also tried using $broadcast and $on but that did not help
Could anyone please help me with it.
Thanks in Advance
I'm not sure to understand your request.. Your code doesn't make any sense, where is defined your data variable ? (controller: line 2), where is defined your fn function ? (html: line 4).
You got the error undefined is not a function which not surprising me because you never defined $scope.loadData method...
I tried to understand your question and produced this code snippet :
angular.module('demo', []);
angular.module('demo')
.directive('custom', function () {
return {
restrict: 'E',
template: '<div>{{ data || "loading..." }}</div>',
scope: {
loadData: '=',
page: '='
},
link: function (scope) {
scope.loadData(scope.page, function (data) {
scope.data = data;
});
}
};
});
angular.module('demo')
.controller('MainCtrl', function ($scope, $timeout) {
$scope.loadData = function (page, done) {
$timeout(function () {
done('data loaded data from ' + page.name);
}, 1000);
};
$scope.pages = [
{ name: 'page 1' },
{ name: 'page 2' },
{ name: 'page 3' }
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="demo" ng-controller="MainCtrl">
<div ng-repeat="page in pages">
<custom load-data="loadData" page="page"></custom>
</div>
</div>
Maybe this could help you.
I have this code, written with Angular 1.2: http://jsfiddle.net/VmkQy/1/
<div ng-app="app">
Title is: <span my-directive data-title="Test title">{{ title }}</span>
</div>
angular.module('app', [])
.directive('myDirective', [function() {
return {
restrict: 'A',
scope: {title:'#'},
link: function($scope) {
alert($scope.title);
}
}
}])
;
Scope has a title property, but it does not rendered. Why?
If I change directive config to scope:true, it will works fine: http://jsfiddle.net/VmkQy/2/
angular.module('app', [])
.directive('myDirective', [function() {
return {
restrict: 'A',
scope: true,
link: function($scope, $element, attrs) {
$scope.title = attrs.title;
alert($scope.title);
}
}
}])
;
This is a bug or feature in Angular 1.2? Older version works fine in all this cases: http://jsfiddle.net/VmkQy/3/
The {{title}} inside of your <span /> gets replaced. Add template: "{{title}}" to your directive and it works:
http://jsfiddle.net/VmkQy/5/