I'm using ui.grid to get a list of parts. I've created a column that contains a button which launches a modal. What I'm having trouble with is sharing the scope of the part that is contained in the row. I want to share the properties of that row with the the button that I'm creating using cellTemplate. I then want to share the $scope of the part row with the modal that it will launch.
I'm a bit stumped on how to actually do this.
So far I've tried
• Wrapping an ng-repeat around the button that I want to target. This kind of works but makes the app super slow
• Data-binding on the button via ng-class. I can't seem to target this correctly.
How can you share the $scope of an object that you're receiving via $http.get into the ui.grid with elements that you're creating with cellTemplate?
Disclaimer -- I always use controllerAs syntax, so if referencing the controller in the context of HTML is weird to you, just ignore that part and pretend like you setup the methods to be directly on the scope. I also do everything in Typescript, not Javascript, so I'm going to write the pertinent parts of the code in here. They should be easy to plug into your application.
The answer is a combination of the two answers you already have from Sunil and S.Baggy.
What you want to do is use the getExternalScopes() function and attach something to the scope of the HTML where your grid resides. The thing you handed the grid will take in the row and call your modal popup. See below for a little clarification.
Your HTML -
<div ng-controller="MyController as myController">
<div ui-grid="myController.GridObject" external-scopes="myController"></div>
</div>
By using controllerAs syntax and making the controller the reference in the external scopes, we can now gain access to everything in our controller. So we can call methods in it.. In order to do that, however, we have to use a cellTemplate, which it sounds like you already know how to do, and in that cellTemplate we have to have the following:
ng-click="getExternalScopes().methodToLaunchModal()"
Now the last part of hooking all this up is to write the methodToLaunchModal() method into the controller. For that we're borrowing the code from S.Baggy's answer. Here is a very abbreviated controller with the GridObject (the same one I referenced from the controller above):
app.controller('MainCtrl', function($scope, $modal) {
GridObject = {
... setup of all the other things
columnDefs: [{ etc, etc, }, { etc, cellTemplate: '<div ng-click="getExternalScopes().methodToLaunchModal(row.entity)">whatever</div>' }]
};
methodToLaunchModal: function(row) {
var modalInstance = $modal.open({
templateUrl: 'someTemplate',
controller: 'ModalController',
resolve: {
rowObject: function () { return row; }
}
});
};
});
At this point your modal scope will have an object named rowObject on it that will have all the properties from your row. So you should be able to call rowObject.SomeProperty to get its value.
Apologies if any of the syntax is slightly off.
I use the bootstrap $modal directive with code like this...
clickFunction: function (event, row) {
event.stopPropagation(); // prevents the current row from appearing as selected
var modalInstance = $modal.open({
templateUrl: 'views/modalcontent.tpl.html',
controller: 'ModalMessageController',
size: 'lg',
resolve: {
message: function () { return row.entity.serial_number; }
}
}
);
Then I just refer to {{message}} in the template. Of course you could pass in any other piece of data too.
You can access row and its properties on row selection or ng-click of that row using externalscopes
ng-click="getExternalScopes().onRowClick(row)"
onRowClick: function (row) {
row.entity.Property1; /// and so on for all row properties
}
Related
I'm using a template called Gentelella and I'm trying to implement AngularJS into it. However, I'm having an issue with a certain Javascript file. In the end of this file, a $(document).ready function is called which initialises Javascript code that makes some changes in the HTML code. The issue is that the $(document).ready function is called too early, before the HTML is fully loaded.
This issue occurs probably because I'm using ngRoute and this injects the template html file into the ng-view of the index.html. When this happens, the DOM probably already announces a document ready before AngularJS has injected the template (=HTML).
So basically, I just need to find a way to call some code in a Javascript file once AngularJS has injected the template.
I attached some code to gain some insight into the issue:
Snippet of the custom.min.js
$(document).ready(function () {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
});
Snippet of the main.js:
.config(function($routeProvider, $httpProvider) {
$routeProvider.when('/', {
templateUrl : 'dash.html',
controller : 'dash',
controllerAs: 'controller'
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation',
controllerAs: 'controller'
}).when('/plain_page', {
templateUrl : 'plain_page.html',
controller : 'dash',
controllerAs: 'controller'
}).otherwise('/');
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
})
Thanks in advance!
Many jQuery plugins depend on a workflow of 1. draw the DOM. 2. run an init() function to set up code against those DOM elements.
That workflow fares poorly in Angular, because the DOM isn't static: Angular sets up and destroys DOM nodes on its own lifecycle, which can overwrite event bindings or DOM changes made outside Angular. Document ready isn't particularly useful when you're using Angular, because all it indicates is that Angular itself is ready to start running.
To use Angular effectively, you have to get into the habit of initing code only when it's actually needed. So instead of a big bucket of init_foo(); init_bar(); on document.ready, you should have a Foo directive with its own init code, and a Bar directive with its own specific init code, and so on. Each of those directives should only modify the DOM created by that specific directive. This is the only safe way to ensure that the DOM elements you need to modify actually exist, and that you're not creating conflicts or unexpected interdependencies between directives.
To take one example: I'm guessing your init_flot_chart() crawls down through the DOM looking for a particular element inside of which it'll draw a flot chart. Instead of that top-down approach, create a directive:
angular.module('yourApp')
.directive('flotWrapper', function () {
return {
template: "<div></div>",
scope: {
data: '#'
},
link: function(scope, elem, attrs) {
var options = {}; // or could pass this in as an attribute if desired
$.plot(elem, scope.data, options); // <-- this calls flot on the directive's element; no DOM crawling necessary
}
};
});
which you use like this:
<flot-wrapper data="{{theChartData}}"></flot-wrapper>
...where theChartData is an object containing whatever data is to be drawn in the chart. (You can add other attributes to pass in whatever other parameters you like, such as the flot options, a title, etc.)
When Angular draws that flotWrapper directive, it first creates the DOM element(s) in the directive template, and then runs whatever is in its link function against the template's root element. (The flot library itself can be included via a plain old <script> tag so its plot function is available when the directive needs it.)
(Note that this wouldn't update automatically if the contents of theChartData change; a more elaborate example which also watches for changes and responds appropriately can be seen here.)
As you are using ngRoute, your controller will run when the page is loaded.
You can call an init() method when the controller starts to do whatever you want.
function MyCtrl($scope) {
function init() {
console.log('controller started!');
}
init();
}
As a side note, it is a best practice recommanded in John Papa Angular's guide.
Another possibily is to use the ng-init directive, for example:
<div ng-controller="MyCtrl" ng-init="myFunction()">...</div>
There is a lifecycle hooks named $postLink() in recent version of angular.js, Called after this controller's element and its children have been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation.Check the guide
This is related to angular digest cycle, it's about how angular works underneath the hood, data binding etc. There are great tutorials explaining this.
To solve your problem, use $timeout, it will make the code execute on the next cycle:
app.controller('Controller', function ($scope, $timeout) {
$scope.$on('$viewContentLoaded', function(event) {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
},0);
});
});
$document.ready(function() {
$scope.$on('$viewContentLoaded', function() {
$timeout(function() {
init_sparklines(), init_flot_chart(), init_sidebar(), init_wysiwyg(), init_InputMask(), ...
})
})
})
update Notice: sry guys, this is not a rigorous solution, plz see another answer I wrote
I am looking for advice on how to implement a hierarchical structure in Angular, where a directive (<partition>) can call a method on a child directive's controller (<property-value>).
I have put together a detailed example here:
https://jsfiddle.net/95kjjxkh/1/
As you can see, my code contains an outer directive, <partition>, which displays one or more <property-value> directives within.
The <property-value> directive offers an editing method, editItem(), which allows the user to change the value of a single entry. (To keep my example short, I simply assign a random number here, but in my production app, a modal will appear, to query the user for a new value.)
This works fine. However, in the outer directive, <partition>, I would like to add the ability to create a new, blank <property-value> directive and then immediately call its editing method so that the user can enter an initial value. If no initial value is entered, the new item would be discarded.
I have seen examples of inner directives calling methods on enclosing directives, but not the other way around.
Is there a way to do this? Alternatively, is there a better way for me to build this kind of view?
You can always use $broadcast to talk both ways. To your parent as well as to your childrens.
In your Child controller you can do the following
app.directive('propertyValue', function() {
return {
require : '^partition'
restrict: 'E',
scope: {
item: '='
},
with this you will get the parent controller in child directive's link function like this
link:function(scope,element,attrs,partitionCtrl){
partitionCtrl.getChildCtrl(element)
}
in partition controller create getChildCtrl function and with that call "propertyvalue" controller function
controller: function ($scope, ItemFactory) {
// your code
var propValueCtrl =undefined;
this.getChildCtrl =function(elem)
{
propValueCtrl = elem.controller();
}
this.callChildFunction = function()
{
propValueCtrl.Edit();// whatever is the name of function
}
call this function when needed in property link function.
Hope this helps.
I've inherited another legacy system that a previous colleague was working on, he was asked to remove server side components from an app and replace them with AngularJS... it's an old JSP system and everything is a bit of a hack. Anyway, there is a jQuery library within the old system that produces Modal Windows, rather than have to add UI-Bootstrap my colleague converted this jQuery service to an AngularJS directive and it kind of works... but with one exception, here is the directive code...
.directive('popupWindow', ['$document', '$compile', function ($document, $compile, MessagesService) {
"use strict";
return {
restrict: 'A',
scope: {},
link: function (scope, element, attrs) {
var newPopup = new Basebox({
contentUrl: '/ng/xtemplate/widgets/CCC/' + scope.getType + '.html',
type: 'regular',
id: 'sf-basebox',
hideBackground: false,
overlay: true,
resolveTest: {getType: scope.getType, message: scope.message, config: scope.config }, // this is appended to the Modal Window as a ng-init and it renders correctly!
closeOnOverlay: false,
closeIcon: true,
checkOldIe: true,
afterContentLoad: function () {
$compile($document.find("#basebox"))(scope);
scope.$apply();
}
});
newPopup.create();
}
});
}
};
}]);
Now this works as it allows us to import HTML templates, it produces the Modal Window and allows us to set various properties, it also allows is to pass data objects (see the resolveTest property) to the controller of the model window. Everything works! Anything within the angularJS templating {{ }} is rendered and we can use angular directives such as ng-init, ng-repeat, etc, etc... however when trying to have two way binding between the view and the controller nothing seems to work, the data gets rendered from the controller to the view but not the other way around. The view never updates... here is the view and the controller
.controller('reportModelCtrl', function ($scope) {
$scope.val = {};
$scope.val.foo = 'blue';
})
Here is the view...
<div ng-controller="reportModelCtrl">
<input type="text" ng-model="val.foo"> {{ val.foo }} this is by default 'blue' but never updates
</div>
Like I say, I dynamically add an ng-init to the modal template and this works, but when I amend the text box nothing happens. Can anyone think of something I'm missing? Even if I try to update values using methods in the controller and ng-click in the view nothing is updated. Calling $scope.$apply() just results in an error. Do I need to add a form to my view? Sorry the jQuery Basebox library isn't included here but I just thought it would add to the confusion. I just really want suggestions or hints to getting the two way binding to work rather than a full answer.
I have an Angular app where I'm using ui-grid. I want to have a custom action on a cell of the grid that calls a method from my app. So basically, this means calling a method that's somewhere up in the parent hierarchy, from a directive.
This would be achieved by calling something like: $scope.$parent.$parent.$parent.$parent.foo(). But that doesn't seem too nice.
One option would be to create a recursive function that goes up the ancestry of the $scope. That's nicer, but still seems a bit weird.
Also... Is it good practice to try to achieve something like this?
You're correct that $parent.$parent.$parent is definitely not a good practice.
If the method you're calling is another directive, you can require that directive in your child directive and then, the parentDirective's controller function will be injected as the fourth parameter to your link function:
In your DDO:
return {
require : '^parentDirective',
restrict : 'E',
link : function (scope, elem, attrs, parentDirectiveController) {}
}
If what you're trying to call is on a factory/service, you can inject that factory/service into your directive, although this sometimes is a code smell, depending on what you're trying to inject.
Finally, another way to do it is to use event propagation. From your directive, you can use $scope.$emit to send information up to parent controllers:
From the directive:
$scope.$emit('directiveDidStuff', {
data : 'blah'
});
In the parent controller:
$scope.$on('directiveDidStuff', function (evt, params) {
this.data = params.data; // equals blah
});
You can achieve the same by using "&" through one of the scope variable in directive.Like this, you can bind your event to the controller method and from the method, you could do your desired things or if the original business logic which you wants to achieve on onClick of the grid is used across many modules than you can bisect it in service and make it reusable and call the service from the event method. Let me know if you do have any doubts with the approach.
Key Code of example:
Html
<my-component attribute-foo="{{foo}}" binding-foo="foo" isolated-expression- foo="updateFoo(newFoo)" >
Directive
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'#attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})
I am trying to call (or use) few custom directives in ionic framework, dynamic is like <mydir-{{type}} where {{type}} will come from services and scope variable, having values radio, checkbox, select etc, and created my directives as mydirRadio, MydirCheckbox, mydirSelect, But its not working.
Is their any good approach to get the dynamic html as per {{type}} in scope?
Long story short; no you can't load directives dynamically in that way.
There are a few options for what you can do. You can, as other answers have mentioned, pass your context as an attribute (mydir type="checkbox"). You could make a directive that dynamically loads another directive, as also mentioned by others. Neither of these options are imo every good.
The first option only works if you write the directive yourself, not when using something like ionic. It also requires you to write multiple directives as one, which can get very messy very quickly. This mega directive will become hard to test and easy to mess up when maintaining it in the future. Note that this is the correct way to pass data to a directive from the view, it's just not good for this specific use case.
The second option is problematic because obfuscates things a bit too much. If someone reads your html and sees a directive called dynamic that is given dynamic data... they have no idea what is going to happen. If they see a directive called dropdown that is given a list they have a fair idea of what the result will be. Readability is important, don't skimp on it.
So I would suggest something simpler that requires much less work from you. Just use a switch:
<div ng-switch="type">
<mydir-select ng-switch-when="select"></mydir-select>
<mydir-checkbox ng-switch-when="checkbox"></mydir-checkbox>
</div>
I dont understand why do you need dynamic directives.
Simple use single directive and change the template accordingly.
For example -
angular.module('testApp')
.directive('dynamicDirective', function($compile,$templateCache,$http) {
return {
restrict: 'C',
link: function($scope,el) {
//get template
if(radio){
$http.get('radio.html', {cache: $templateCache}).success(function(html){
//do the things
el.replaceWith($compile(html)($scope));
});
} else if(checkbox){
//load checkbox template
} //vice-versa
}
};
});
You can inject service variable in directive also.
a bit more code would help. I don't know, if its possible to do dynamic directives like the ones in a tag
<{dyntag}></{dyntag}>
but you also can use an expression like
<your-tag dynamic_element="{type}">...</your-tag>
which should have exactly the same functionality. In your case it would be like:
Your JSObject ($scope.dynamics):
{"radio", "checkbox", "select"}
and your HTML:
<div ng-repeat="dyn in dynamics">
<your-tag dynamic_element="{dyn}"></your-tag>
</div>
Yes, that's not a problem. You can interpolate your data using {{}} and in your directive compile a new element using that data:
myApp.directive('dynamic', function($compile, $timeout) {
return {
restrict: "E",
scope: {
data: "#var" // say data is `my-directive`
},
template: '<div></div>',
link: function (scope, element, attr) {
var dynamicDirective = '<' + scope.data + ' var="this works!"><' + scope.data + '>';
var el = $compile(dynamicDirective)(scope);
element.parent().append( el );
}
}
});
HTML:
<div ng-controller="MyCtrl">
<dynamic var="{{test}}"></dynamic>
</div>
Fiddle