Angular directive with ng-template - angularjs

Today I'm trying to develop a popover directive. I don't know why the ng-repeat inside the styles-select directive wich is insered in the popover after click doesn't work(<- Edited it works now)... And I want to get the value of "selectedStyles" in my controller "MyController" without passing it through the directive.
var app = angular.module('MyApp', []);
app.controller('MyController', ['$scope', function($scope) {
$scope.selectedStyles = [];
$scope.$watch('selectedStyles', function(newValue, oldValue) {
console.log(newValue);
});
}]);
app.directive('popover', ['$compile', '$templateCache', function($compile, $templateCache) {
return {
restrict: 'A',
scope: {
header: '#header',
template: '=template'
},
link: function(scope, element) {
element[0].onclick = function (event) {
var popover = document.createElement('div'),
header = document.createElement('h4'),
content = document.createElement('p');
header.textContent = scope.header;
content.innerHTML = $templateCache.get(scope.template);
popover.appendChild(header);
popover.appendChild(content);
document.body.appendChild($compile(popover)(scope)[0]);
scope.$apply();
}
}
};
}]);
app.directive('stylesSelect', ['$compile', '$filter', function($compile, $filter) {
return {
restrict: 'E',
scope: {
selectedStyles: '=selectedStyles'
},
template: '<div ng-repeat="s in styles"><label><input type="checkbox" ng-model="s.selected" ng-change="selectStyle()" /> {{s.label}}</label></div>',
link: function(scope, element) {
scope.styles = [
{label: 'Hipster', selected: false},
{label: 'Hip-Hop', selected: false},
{label: 'Punk', selected: false}
];
scope.selectStyle = function() {
scope.selectedStyles = $filter('filter')(scope.styles, {selected: true});
};
}
}
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
<div ng-app="MyApp">
<div ng-controller="MyController">
{{test}}
<button popover template="'popoverContent.html'" header="Select your styles" type="button">Show Popover</button>
<script type="text/ng-template" id="popoverContent.html">
<styles-select selected-styles="selectedStyles"></styles-select>
</script>
</div>
</div>
It gonna make me crazy... Please Help lol
Thank you

Instead of changing values in different scopes, try to use a service with a promise. This way the popover service is more reusable in your application.
var app = angular.module('MyApp', []);
app.controller('MyController', ['$scope', 'popover',
function($scope, popover) {
$scope.selectedStyles = [];
$scope.showStylesSelect = function() {
popover.show({
templateUrl: 'popoverContent.html',
scope: {
header: 'Select your style',
styles: [{
label: 'Hipster',
selected: false
}, {
label: 'Hip-Hop',
selected: false
}, {
label: 'Punk',
selected: false
}]
}
}).then(function(result) {
$scope.selectedStyles = result.selectedStyles;
});
};
$scope.$watch('selectedStyles', function(newValue, oldValue) {
console.log(newValue);
});
}
]);
app.factory('popover', ['$rootScope', '$q', '$compile', '$templateCache',
function($rootScope, $q, $compile, $templateCache) {
function showPopover(options) {
var defer = $q.defer(),
scope = $rootScope.$new(),
popover = document.createElement('div'),
header = document.createElement('h4'),
content = document.createElement('p');
angular.extend(scope, options.scope || {});
scope.close = function() {
popover.parentNode.removeChild(popover);
defer.resolve(scope);
};
header.textContent = options.header || '';
content.innerHTML = $templateCache.get(options.templateUrl);
popover.appendChild(header);
popover.appendChild(content);
document.body.appendChild($compile(popover)(scope)[0]);
return defer.promise;
}
return {
show: showPopover
}
}
]);
app.directive('stylesSelect', ['$filter',
function($filter) {
return {
restrict: 'E',
scope: false,
template: '<div ng-repeat="s in styles"><label><input type="checkbox" ng-model="s.selected" ng-change="selectStyle()" /> {{s.label}}</label></div><button ng-click="close()">close</button>',
link: function(scope) {
scope.selectStyle = function() {
scope.selectedStyles = $filter('filter')(scope.styles, {
selected: true
});
};
}
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
<div ng-app="MyApp" class="ng-scope">
<script type="text/ng-template" id="popoverContent.html">
<styles-select selected-styles="selectedStyles"></styles-select>
</script>
<div ng-controller="MyController" class="ng-scope ng-binding">
<button ng-click="showStylesSelect()">Show Popover</button>
</div>
</div>

Related

How to expose directive methods using a service

How to expose directive methods without using $broadcast or '=' between modules?
Using $broadcast (events) if there are multiple directives all will be notified. It cannot return value too.
Exposing directive's function by html attribute I think it is not that best that Angular has to offer.
Angular Bootstrap UI do it using services (I guess): It have a service named "$uibModal".
You can call a function "$uibModal.open()" of Modal Directive by injecting $uibModal service.
Is that the right way?
An example of a directive that registers its API with a service:
app.service("apiService", function() {
var apiHash = {};
this.addApi = function (name,api) {
apiHash[name] = api;
};
this.removeApi = function (name) {
delete apiHash[name];
};
this.getApi = function (name) {
return apiHash[name];
};
});
app.directive("myDirective", function (apiService) {
return {
restrict: 'E',
scope: {},
template: `<h1>{{title}}</h1>`,
link: postLink
};
function postLink(scope, elem, attrs)
var name = attrs.name || 'myDirective';
var api = {};
api.setTitle = function(value) {
scope.title = value;
};
apiService.addApi(name, api);
scope.$on("$destroy", function() {
apiService.removeApi(name);
});
}
});
Elsewhere in the app, the title of the directive can be set with:
apiService.getApi('myDirective').setTitle("New Title");
Notice that the directive registers the api with a name determined by the name attribute of the directive. To avoid memory leaks, it unregisters itself when the scope is destroyed.
Update
How could I use it from a controller?
app.controller('home', function($scope,apiService) {
$scope.title = "New Title";
$scope.setTitle = function() {
apiService.getApi('mainTitle').setTitle($scope.title);
};
})
<body ng-controller="home">
<my-directive name="mainTitle"></my-directive>
<p>
<input ng-model="title" />
<button ng-click="setTitle()">Set Title
</button>
</p>
</body>
The DEMO
angular.module('myApp', [])
.service("apiService", function() {
var apiHash = {};
this.addApi = function(name, api) {
apiHash[name] = api;
};
this.getApi = function(name) {
return apiHash[name];
};
})
.directive("myDirective", function(apiService) {
return {
restrict: 'E',
scope: {},
template: `<h1>{{title}}</h1>`,
link: postLink
};
function postLink(scope, elem, attrs) {
var name = attrs.name || 'myDirective';
var api = {};
api.setTitle = function(value) {
scope.title = value;
};
apiService.addApi(name, api);
scope.$on("$destroy", function() {
apiService.addApi(name, null);
});
}
})
.controller('home', function($scope,apiService) {
$scope.title = "New Title";
$scope.setTitle = function() {
apiService.getApi('mainTitle').setTitle($scope.title);
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myApp" ng-controller="home">
<my-directive name="mainTitle"></my-directive>
<p>
<input ng-model="title" />
<button ng-click="setTitle()">Set Title
</button>
</p>
</body>
.factory('myService', [function() {
return {
charCount: function(inputString) {
return inputString.length;
}
}
}])
this service exposes function charCount();
in your directive you have to inject it like this
.directive('testDirective', ['myService', function(myService) {
return {
restrict: 'A',
replace: true,
template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
link: function($scope, el, attrs) {
$scope.myTestString = 'string of length 19';
$scope.strLen = myService.charCount( $scope.myTestString );
}
}
}])
and, of course call it
$scope.strLen = myService.charCount( $scope.myTestString );
<html>
<style>
#out {
width:96%;
height:25%;
padding:10px;
border:3px dashed blue;
font-family: monospace;
font-size: 15px;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var APP = angular.module('MYAPP', []);
APP.controller('main', ['$scope', '$element', '$compile', 'myService', function($scope, $element, $compile, myService) {
$scope.test = 'my Test Controller';
$scope.directiveTest = "directive test";
var testSvc = myService.charCount($scope.test);
$scope.showTestDir = true;
}])
.directive('testDirective', ['myService', function(myService) {
return {
restrict: 'A',
replace: true,
template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
link: function($scope, el, attrs) {
$scope.myTestString = 'string of length 19';
$scope.strLen = myService.charCount( $scope.myTestString );
}
}
}])
.factory('myService', [function() {
return {
charCount: function(inputString) {
return inputString.length;
}
}
}])
.filter('toUpper', function() {
return function(input) {
return input.toUpperCase();
}
})
.filter('toLower', function() {
return function(input) {
return input.toLowerCase();
}
})
;
</script>
<body ng-app="MYAPP">
<div id="out" ng-controller="main">
{{test}} - not filtered
<br/>
{{test|toUpper}} - filtered toUpper
<br/>
{{test|toLower}} - filtered toLower
<br/>
<br/>
<div test-directive ng-if="showTestDir"></div>
</div>
</body>
</html>

Can't access directive scope from within controller

I have an issue with my AngularJS code.
It seems I can't access my directive scope from my controller.
The directive
app.directive('PaginatedText', function(){
return {
restrict: 'E',
scope: {
text: '='
},
controller: 'PaginatedTextController',
templateUrl: '/frontend/views/paginated-text.html'
};
})
The controller
app.controller('PaginatedTextController', ['$scope', function($scope){
$scope.page = 1;
$scope.limit = 200;
console.log($scope, $scope.text);
}]);
The directive call
<paginated-text text="character.long_description"></paginated-text>
The template
<p>
{{text}}
<button>read more</button>
</p>
The printed console.log:
Any idea why console.log($scope.text) print undefined ?
Directive names must start with lowercase!
app.directive('paginatedText',
angular.module('app', [])
.run(function($rootScope) {
$rootScope.character = {
long_description: 'LirumLarum'
};
})
.directive('paginatedText', function() {
return {
restrict: 'E',
scope: {
text: '='
},
controller: 'PaginatedTextController',
template: '<p>{{text}}<button>read more</button></p>'
};
})
.controller('PaginatedTextController', ['$scope',
function($scope) {
$scope.page = 1;
$scope.limit = 200;
console.log($scope, $scope.text);
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="app">
<paginated-text text="character.long_description"></paginated-text>
</div>

Passing scope object to Angular Directive via $compile

I know that if I have a directive in HTML, I can pass objects from my $scope to it:
<mydirective some-data="someData"></mydirective>
...where someData might be JSON object.
Is it possible to pass a reference to someData if I create the directive dynamically and use $compile?
$scope.someData = { firstName: 'John', lastName: 'Doe' };
var link = $compile("<mydirective some-data='???'></mydirective>");
var content = link($scope);
$(body).append(content);
Yes you can pass object to
var myApp = angular.module('myApp',[]);
myApp.directive('passObject', function() {
return {
restrict: 'E',
scope: { obj: '=' },
template: '<div>Hello, {{obj.prop}}!</div>'
};
});
myApp.controller('MyCtrl', function ($scope) {
$scope.obj = { prop: "world" };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.min.js"></script>
<div ng-controller="MyCtrl">
<pass-object obj="obj"></pass-object>
</div>
var myApp = angular.module('myApp',[]);
myApp.directive('passObject', function() {
return {
restrict: 'E',
scope: { obj: '=' },
template: '<div>Hello, {{obj.prop}}!</div>'
};
});
myApp.controller('MyCtrl', function ($scope) {
$scope.obj = { prop: "world" };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<div ng-controller="MyCtrl">
<pass-object obj="obj"></pass-object>
</div>
directive

AngularJS - custom filter, model doesn't update in view

I have a custom filter set up that triggers when the user clicks on a checkbox. I use a directive to render the DOM elements, and attach a listener on the checkbox which when clicked, triggers the filter function that's exposed on the $scope.
The $scope.model which is used in the view should get overwritten by the result of the filter function, and the return object looks ok (e.g. the console.log()) but the view doesn't update. What am I doing wrong?
http://jsfiddle.net/r3pXc/1/
The view:
<body ng-app="app">
<div ng-controller="mainCtrl">
<div list-directive />
</div>
The template:
<script type="text/ng-template" id="list.html">
<input type="checkbox" class="control">
<div ng-repeat="player in model">
Name: {{player.firstName}}, Price: {{player.price}}
</div>
</script>
The module and controller:
var app = angular.module('app', []);
app.controller('mainCtrl', ['$scope', '$filter', function($scope, $filter){
$scope.model = [{ firstName: 'foo', price: 100 }, { firstName: 'bar', price: 50 }, { firstName: 'foobar', price: 0}];
$scope.filter = function() {
$scope.model = $filter('listFilter')($scope.model);
console.log($scope.model);
}
}]);
The directive:
app.directive('listDirective', function(){
return {
restrict: 'AE',
templateUrl: 'list.html',
link: function($scope, iElm, iAttrs, controller) {
iElm.bind('click', function(e){
var el = angular.element(e.target);
if (el.hasClass('control')) {
$scope.filter();
};
});
}
};
});
And the filter:
app.filter('listFilter', function(){
return function(input) {
var results = [];
angular.forEach(input, function(val, key){
if (val.price != 0) {
results.push(val);
}
});
return results;
}
});
Need to manually call the digest cycle with $apply(), because I don't listen on the elements with ng- event handlers:
$scope.filter = function() {
$scope.model = $filter('listFilter')($scope.model);
$scope.$apply();
}

angularJS: watch async data in directive

I'm trying to watch a async data in my directive, here is my JS code:
(function(angular) {
var myApp = angular.module('testTree', []);
myApp.config(function($httpProvider) {
$httpProvider.defaults.headers.get = {};
$httpProvider.defaults.headers.get["Content-Type"] = "application/json";
});
myApp.factory('DataService', function($http) {
return { getData: function(prmParentId, prmParentSrc) {
var data = $.param({ 'parentId': prmParentId, 'parentSrc': prmParentSrc });
return $http.get("Temp.aspx/GetData", { params: { parentId: prmParentId, parentSrc: prmParentSrc }, data: '' }).
success(function(result) {
return result.d;
});
}
}
});
myApp.controller('myController', myController);
function myController($scope, DataService) {
$scope.treeNodes = [];
$scope.init = function() {
DataService.getData(0, 0).then(function(promise) {
$scope.treeNodes = promise.data.d;
});
}
$scope.focusNode = function() {
console.log("kuku2");
}
}
myApp.directive('nodes', function() {
return {
restrict: "E",
replace: true,
scope: {
nodes: '=',
clickFn: '&'
},
template: "<ul><node ng-repeat='node in nodes' node='node' click-fn='clickFn()'></node></ul>",
link: function(scope, element, attrs) {
scope.$watch('treeNodes', function(newVal, oldVal) {
console.log(scope.treeNodes);
}, true);
}
}
});
myApp.directive('node', function($compile) {
return {
restrict: "E",
replace: true,
scope: {
node: '=',
clickFn: '&'
},
template: "<li><span ng-click='clickFn()(node)'>{{node.ObjectName}}</span></li>",
link: function(scope, element, attrs) {
if (angular.isArray(scope.node.Children)) {
element.append("<nodes nodes='node.Children' click-fn='clickFn()'></nodes>");
$compile('<nodes nodes="node.Children" click-fn="clickFn()"></nodes>')(scope, function(cloned, scope) {
element.append(cloned);
});
}
}
}
});
})(angular);
And this is my HTML:
<div ng-app="testTree">
<div ng-controller="myController">
<div ng-init="init()">
<nodes node="treeNodes" click-fn="focusNode"></nodes>
</div>
</div>
</div>
The console in the directive's watch is always "undefined". What am I doing wrong here?
thanks.
Pass treeNodes as nodes in your directive. So you need to watch nodes.
scope.$watch('nodes', function(newVal, oldVal) {
console.log($scope.nodes);
}, true);
<div ng-app="testTree">
<div ng-controller="myController">
<div ng-init="init()">
<nodes nodes="treeNodes" click-fn="focusNode"></nodes>
</div>
</div>
</div>

Resources