Create new isolated scope programmatically - angularjs

I am trying to create popup html to go into a leafletjs marker popup.
I have the following partial:
<div class="popup">
<div class="pull-right">
<a class="popup-info" ng-click="onInfoClicked()">
<i class="fa fa-info-circle"></i>
</a>
</div>
<div class="popup-title">
{{title}}
</div>
<div class="popup-subtitle">
{{subtitle}}
</div>
</div>
and the following directive:
app.directive('leafletPopup', function() {
return {
restrict: "A",
templateUrl: "app/partials/leaflet-popup.html",
scope: {
feature: '=',
popupInfo: '&'
},
controller: function ($scope) {
$scope.title = $scope.feature.properties.title;
$scope.subtitle = $scope.feature.properties.description;
$scope.onInfoClicked = function() {
$scope.popupInfo({feature: feature});
}
}
};
});
I have a controller that provides a function to generate the html for each marker that I am going to place on the map:
function html(feature) {
var el = angular.element('<div leaflet-popup="feature" popup-info="onPopupInfo(feature)"></div>');
var compiled = $compile(el);
var newScope = $scope.$new(true); // create new isolate scope
newScope.feature = feature; // inject feature into the scope
newScope.onPopupInfo = function(feature) { // inject function into scope
// do something with the click
showFeatureDetails(feature);
}
compiled(newScope);
return el[0];
}
Works perfectly. Great right?
I have read in a couple of places that its not recommended to create your own scope manually as you have to make sure you also destroy it manually.
Lets say I have a bunch of markers on my map, and each has a popup. Do I need to track all the new scopes I create in my controller and call destroy on them? When? Only if my marker gets removed from the map?
I could just skip angular and build the entire html element with jquery and attach the onclick to a function, but that is also not pretty. And why skip angular?
Seems overly complicated, which probably means I am doing this the hard way ;)

Related

Creating a Reusable Component in AngularJS

I am new to Stackoverflow. I'm also new to AngularJS. I apologize if I'm not using this correctly. Still, I'm trying to create a UI control with AngularJS. My UI control will look like this:
+---------------------+
| |
+---------------------+
Yup. A textbox, which has special features. I plan on putting it on pages with buttons. My thought is as a developer, I would want to type something like this:
<ul class="list-inline" ng-controller="entryController">
<li><my-text-box data="enteredData" /></li>
<li><button class="btn btn-info" ng-click="enterClick();">Enter</button></li>
</ul>
Please note, I do not want buttons in my control. I also want the enteredData to be available on the scope associated with child controls. In other words, when enterClick is called, I want to be able to read the value via $scope.enteredData. In an attempt to create my-text-box, I've built the following directive:
myApp.directive('myTextBox', [function() {
return {
restrict:'E',
scope: {
entered: '='
},
templateUrl: '/templates/myTextBox.html'
};
}]);
myApp.controller('myTextBoxController', ['$scope', function($scope) {
$scope.doSomething = function($item) {
$scope.entered = $item.name;
// Need to somehow call to parent scope here.
};
}]);
myTextBox.html
<div ng-controller="myTextBoxController">
<input type="text" class="form-control" ng-model="query" placeholder="Please enter..." />
</div>
entryController.js
myApp.controller('entryController', ['$scope', function($scope) {
$scope.enteredData = '';
$scope.enterClick = function() {
alert($scope.enteredData);
};
});
Right now, I have two issues.
When enterClick in entryController is called, $scope.enteredData is empty.
When doSomething in myTextBoxController is called, I do not know how to communicate to entryController that something happened.
I feel like I've setup my directive correctly. I'm not sure what I'm doing wrong. Can someone please point me in the correct direction.
Three suggestions for you.
1) You really shouldn't create a directive with a template that references a controller defined elsewhere. It makes the directive impossible to test in isolation and is generally unclear. If you need to pass data into a directive from a parent scope use the isolate scope object on your directive to bind to that data (Note how the directive template doesn't have a controller) http://jsfiddle.net/p4ztunko/
myApp.directive('myTextBox', [function () {
return {
restrict: 'E',
scope: {
data: '='
},
template: '<input type="text" class="form-control" ng-model="data" placeholder="Please enter..." />'
};
}]);
myApp.controller('entryController', ['$scope', function ($scope) {
$scope.enteredData = 'Stuff';
$scope.enterClick = function () {
alert($scope.enteredData);
};
}]);
<div>
<ul class="list-inline" ng-controller="entryController">
<li>{{enteredData}}
<my-text-box data="enteredData" />
</li>
<li>
<button class="btn btn-info" ng-click="enterClick();">Enter</button>
</li>
</ul>
</div>
2) Don't obfuscate HTML when you don't need to. One of the goals of angular is to make the markup more readable, not replace standard elements with random custom elements. E.g. If you want to watch the value of the input and take action depending on what it is you could do that in the linking function (Note: still not referencing an external controller) http://jsfiddle.net/Lkz8c5jo/
myApp.directive('myTextBox', function () {
return {
restrict: 'A',
link: function(scope, element, attrs){
function doSomething (val) {
alert('you typed ' + val);
}
scope.$watch(attrs.ngModel, function (val) {
if(val == 'hello'){
doSomething(val);
}
});
}
};
});
myApp.controller('entryController', ['$scope', function ($scope) {
$scope.enteredData = 'Stuff';
$scope.enterClick = function (data) {
alert('You clicked ' + data);
};
}]);
<div>
<ul class="list-inline" ng-controller="entryController">
<li>{{enteredData}}
<input type="text" ng-model="enteredData" my-text-box />
</li>
<li>
<button class="btn btn-info" ng-click="enterClick(enteredData)">Enter</button>
</li>
</ul>
</div>
3) Pass data into controller functions from the UI instead of referencing the $scope object in the function like in ng-click="enterClick(enteredData)" It makes testing easier because you remove the $scope dependency from that method
$scope.enteredData is empty because you're not using the correct scope. The $scope in entryController is not the same $scope as in myTextBoxController. You need to specify one of these controllers on your directive, and then use that to reference the proper scope.
It seems like you should move the enterClick and corresponding button into your directive template. Then move the enter click into your text box controller and you will be able to reference $scope.enteredData.
You can notify a parent scope of a change by using $emit. (This is in reference to your comment "// Need to somehow call to parent scope here.")
Furthermore, you may have an issue of not using the proper variable. In myTextBox directive, you declare $scope.entered, yet you are effectively setting $scope.data equal to the value of enteredData in the html.

Scope values to a requested content

I have a view that contains a button, when the button is clicked, a $http.get request is executed and the content is appended on the view.
View:
<button ng-click="includeContent()">Include</button>
<div id="container"></div>
Controller:
$scope.includeContent = function() {
$http.get('url').success(function(data) {
document.getElementById('container').innerHTML = data;
}
}
The content to include:
<h1>Hey, I would like to be {{ object }}</h1>
How can I scope a value to object? Do I need to approach this in a complete different way?
The built-in directive ng-bind-html is the way you are looking for.
Beware, that ng-bind-html requires a sanitized string, which is either done automatically when the correct libary is found or it can be done manually ($sce.trustAsHtml).
Don't forget to inject $sce in your controller.
$scope.includeContent = function() {
$http.get('url').success(function(data) {
$scope.data = $sce.trustAsHtml(data);
}
}
<button ng-click="includeContent()">Include</button>
<div ng-bind-html="data"></div>
As you also want to interpolate your requested HTML, I suggest using $interpolate or, if it can contain whole directives or should have a full fledged two-way-data-binding, use $compile instead.
In your case alter the assignment to
$scope.data = $sce.trustAsHtml($interpolate(data)($scope));
Don't forget to inject $interpolate/$compile aswell.
As I don't know about your $scope structure I assume that "object" is available in this scope. If this isn't the case then change the $scope parameter to whatever object contains your interpolation data.
You should use a controller to do this (I imagine you are since you're using $scope).
ctrl function () {
var ctrl = this;
ctrl.includeContent = function () {
$http.get("url").success(function (data) {
ctrl.object = data;
});
};
}
<div ng-controller="ctrl as ctrl">
<button ng-click="ctrl.includeContent()">Include</button>
<div id="container">
<h1 ng-show="ctrl.object">Hey, I would like to be {{ctrl.object}}</h1>
</div>
</div>
You need not select an element and append the data to it. Angular does it for you. That's what is magic about angular.
In your controller's scope, just update object and angular does the heavy-lifting
$scope.includeContent = function() {
$http.get('url').success(function(data) {
$scope.object = data;
}
}
If that's html code from a server, then you should use the 'ng-bind-html' attribute:
<button ng-click="includeContent()">Include</button>
<div id="container" ng-bind-html="htmlModel.ajaxData"></div>
Controller:
$scope.htmlModel = {ajaxData:''};
$scope.includeContent = function() {
$http.get('url').success(function(data) {
$scope.htmlModel.ajaxDataL = data;
}
}
One way is to use ng-bind-html as suggested.
Another way is with $compile:
app.controller('MainCtrl', function($scope, $http, $compile) {
$scope.error='error!!!';
$scope.includeContent = function() {
$http.get('url').success(function(data) {
var elm = angular.element(document.getElementById('container')).html(data);
$compile(elm)($scope);
}).error(function(){
var elm = angular.element(document.getElementById('container')).html('{{error}}');
$compile(elm)($scope);
})
}
});
Also, typically in angular, when you want to manipulate the DOM you use directives.
DEMO

Call a function in a angular-controller from outside of the controller?

I have a lightbox-dierective and controller that looks like this:
directive('modalDialog', function() {
return {
restrict: 'E',
scope: {
show: '='
},
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
template: '<div class="ng-modal" ng-show="show"><div class="ng-modal-overlay" ng-click="hideModal()"></div><div class="ng-modal-dialog" ng-style="dialogStyle"><div class="ng-modal-dialog-content" ng-transclude><div class="ng-modal-close" ng-click="hideModal()">X</div></div></div></div>'
};
}).controller('Lightbox', function($scope) {
$scope.modalShown = false;
$scope.toggleModal = function() {
$scope.modalShown = !$scope.modalShown;
};
});
Here is the desierd html, what I need is to open the secon ligthbox from withing the first one:
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>One lightbox <span ng-mousedown='toggleModal()'>Open lightbox two</span></h2>
</modal-dialog>
</div>
<div ng-controller="Lightbox">
<span ng-mousedown='toggleModal()'>Open lightbox one</span>
<modal-dialog show='modalShown'>
<h2>another lightbox</h2>
</modal-dialog>
</div>
For most cases it works great! I use it in several occations throughout the site, with diffrent lightboxes and different content.
I have now come across a case, where I need to call one of the lightboxes from outside of the controller. Can this be achieved and in that case how do I reference the right lightbox?
I'd extend that setting to an object
var modalSet = {
shown: false,
toggle: function(){ modalSet.shown = !modalSet.shown }
}
Then put it on your main controller (the one with ngApp attribute) and have your entire scope modaleble.
Also, directives do have a controller option, but since only one modal is gonna show up at any given time, you might not want to re-create a controller for every new instance.
Upon re-reading your question: Where is it exactly -> "outside of the controller"?

How do I use an Angular directive to show a dialog?

Using Angular, I'm trying to create a directive that will be placed on a button that will launch a search dialog. There are multiple instances of the search button, but obviously I only want a single instance of the dialog. The dialog should be built from a template URL and have it's own controller, but when the user selects an item, the directive will be used to set the value.
Any ideas on how to create the dialog with it's own controller from the directive?
Here's what I've go so far (basically just the directive)...
http://plnkr.co/edit/W9CHO7pfIo9d7KDe3wH6?p=preview
Here is the html from the above plkr...
Find
Here is the code from the above plkr...
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
var person = {};
person.name = 'World';
$scope.person = person;
$scope.setPerson = function(newPerson) {
person = newPerson;
$scope.person = person;
}
});
app.directive('myFind', function () {
var $dlg; // holds the reference to the dialog. Only 1 per app.
return {
restrict: 'A',
link: function (scope, el, attrs) {
if (!$dlg) {
//todo: create the dialog from a template.
$dlg = true;
}
el.bind('click', function () {
//todo: use the dialog box to search.
// This is just test data to show what I'm trying to accomplish.
alert('Find Person');
var foundPerson = {};
foundPerson.name = 'Brian';
scope.$apply(function () {
scope[attrs.myFind](foundPerson);
});
});
}
}
})
This is as far as I've gotten. I can't quite figure out how to create the dialog using a template inside the directive so it only occurs once and then assign it a controller. I think I can assign the controller inside the template, but first I need to figure out how to load the template and call our custom jQuery plugin to generate the dialog (we have our own look & feel for dialogs).
So I believe the question is, how do I load a template inside of a directive? However, if there is a different way of thinking about this problem, I would be interested in that as well.
I will show you how to do it using bootstrap-ui. (you can modify it easily, if it does not suit your needs).
Here is a skeleton of the template. You can normally bound to any properties and functions that are on directive's scope:
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
... // e.g. <div class="button" ng-click=cancel()></div>
</div>
<div class="modal-body">
...
</div>
<div class="modal-footer">
...
</div>
</div>
</div>
Here is how to create/declare directive in your module:
.directive("searchDialog", function ($modal) {
return {
controller: SearchDialogCtrl,
scope : {
searchDialog: '=' // here you will set two-way data bind with a property from the parent scope
},
link: function (scope, element, attrs) {
element.on("click", function (event) { // when button is clicked we show the dialog
scope.modalInstance = $modal.open({
templateUrl: 'views/search.dialog.tpl.html',
scope: scope // this will pass the isoleted scope of search-dialog to the angular-ui modal
});
scope.$apply();
});
}
}
});
Then controller may look something like that:
function SearchDialogCtrl(dep1, dep2) {
$scope.cancel = function() {
$scope.modalInstance.close(); // the same instance that was created in element.on('click',...)
}
// you can call it from the template: search.dialog.tpl.html
$scope.someFunction = function () { ... }
// it can bind to it in the search.dialog.tpl.html
$scope.someProperty;
...
// this will be two-way bound with some property from the parent field (look below)
// if you want to perform some action on it just use $scope.$watch
$scope.searchDialog;
}
Then it your mark-up you can just use it like that:
<div class="buttonClass" search-dialog="myFieldFromScope">search</div>
I recommend this plugin:
https://github.com/trees4/ng-modal
Demo here:
https://trees4.github.io/ngModal/demo.html
Create a dialog declaratively; and it works with existing controllers. The content of the dialog can be styled however you like.

Changing src of only hovered ng-include element, where value of src is a $scope variable

I have multiple ng-include elements that have src attribute set to $scope.template_url.
I want to change src of hovered element only to new template but changing it's value will change all of elements. How can i implement it?
Html code:
<section class="parent">
<div data-ng-include data-src="template_url"></div>
</section>
Javascript (in controller):
angular.element(document).on('mouseover', '.parent', function(){
$scope.$apply(function () {
$scope.template_url = "path/to/new/template.html";
});
});
Writing jQuery dom manipulation is dirty and also don't works:
$(this).attr('data-src', "path/to/new/template.html");
I'd suggest making this a directive. Directives have their own scope, so you can still do the "on hover use a different template" idea, but for each individual one that is hovered.
<div>
<div data-some-directive=""></div>
</div>
var myApp = angular.module('myApp',[]);
myApp.directive('someDirective', function() {
return {
controller: function ($scope) {
$scope.model = "Hello"
$scope.mouseover = function () {
$scope.model = "Hovered!";
};
},
scope:{},
restrict: 'AE',
replace: true,
template: '<div><input ng-mouseover="mouseover()" ng-model="model"></div>',
};
});
Heres a fiddle to see it in action.
Tweak the template variable in the directive to use a variable on your model for the include url.
By the way, angular already has a mouseover handler, so i've just linked that into the controller with ng-mouseover in the template.

Resources