I have a custom scope method within a Controller, and when a custom directive loads, I want to run a method inside the defined controller. I've seen a lot of options, but which one could be referenced inside a template via an ng-* call? Otherwise, what are the best options?
Since the controller is instantiated when the directive is loaded, any method called in your controller will be called on page load. In my code it is often something like
angular.module('app')
.controller('controllerName', ctrl);
function ctrl() {
/*--------Initialize--------*/
someMethod()
}
If you are on angular 1.5 already and can use the new component way to build your custom directive, you could use the newly introduced $onInit method instead of polluting the constructor, that should only initalize the object itself.
For details, please see this blogpost: https://toddmotto.com/on-init-require-object-syntax-angular-component/
Related
I have a one-page site that I am building out and this is my first time using Angular on a site. Building it on top of Laravel too for the backend but that is beyond the scope of this question.
I need to be able to open a modal on a main page view which will add a new resource (e.g. a new client) or edit a resource. I want to somehow get the form's html inside the modal body when the $uibModal.open()'s controller is called and set the $scope.modalBody equal to the injected items.modalBody (the only way this works is if I use:
$scope.modalBody = $sce.trustAsHtml(items.modalBody);
The only problem now is that anything inside the HTML body, Angular will not use it's magic and do any data-binding. It is still in the raw form of
{{ object.property }} or since I'm using Laravel and avoiding conflict with the Blade template engine:
<% object.property %>
See screenshot:
screenshot
I have been banging my head against the wall on this one...I have tried putting $scope.$apply() in my directive and my controller, neither of which worked. I have a feeling that is the source of my problem though. I have also tried making the html just a <new-client></new-client> directive and using templateUrl: 'views/clients/add.php' which would be ideal, but the template is not being included inside the <new-client></new-client>.
I'm using ui-bootstrap 0.14.3 and Angular 1.4.8.
Could this be a bug? Or am I doing something wrong? Anyone have a better way of getting a form into my modal? Let me know what code you want to see so I don't clutter this post with unnecessary code blocks.
I have come across a similar issue with using jQuery's AJAX to receive template strings and append it to a server.
So when HTML is added via jQuery, bound html string, etc., angular doesn't know it needs to automagically compile this data.
What you need to do is use the $compile service, to $compile your html and then attach the correct $scope to it:
`$compile('jQuerySelectorReturningHtmlOrAnHTMLStringThatNeedsToBeCompiled')($scope);`
There are multiple examples in Angulars Documentation for $compile that can give you an idea of what is happening. I think by what you have described the same thing is happening here in your situation.
The key is to call this $compile service function after the html has been bound to the page.
EDIT:
There are a few other options based on some comments, that will serve as a viable solution to rendering this content on your view. For example a directive that takes a string attribute representing the HTML string of your desired view.
1. Modify your directive template in the compile step:
You have the ability to modify your template before the directive compiles and binds any attributes to it, to that directives scope:
app.directive('myAwesomeCompileStepDirective', [myAwesomeCompileStepDirectivef]);
function myAwesomeCompileStepDirectiveFn() {
return {
restrict: 'EA',
compile: function compileFn(tAttrs, tElement) {
//Here you can access the attrs that are passed into your directive (aka html string)
tElement.html(tAttrs['stringThatYouWantToReplaceElementWith']);
return function linkFn(scope, element, attrs, controller, transcludeFn) {
//if all you want to do is update the template you really don't have to do anything
//here but I leave it defined anyways.
}
}
}
}
You can view a file I wrote for a npm component which uses this method to modify my directive template before it is compiled on the page & you can also view the codepen for the complete component to see it in action.
2. Use $compile service to call $compile in link function using directive attrs.
In the same way as the aforementioned method, you can instead inject the $compile service, and call the function mentioned above. This provides a bit more work, for you but more flexibility to listen to events and perform scope based functions which is not available in the compile function in option 1.
According to the Angular docs:
Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
Now my Question is : if my function is not connected to the view, should we use $scope or not?
I assume that you mean if you should do $scope.functionName = function(), even if the function isn't connected to the view.
No you shouldn't, why would you expose a function to the view, which isn't needed to the view? Also you get a better overview which functions is internally when only using function funcName().
You shouldn't use the $scope to declare every function you are using, especially if it's not connected to the view.
However, there are some cases you need to use the $scope in a function not connected to view, for example if you want to emit/receive/broadcast a message on the scope tree or access something on a parent scope (although it's not necessarly a good practice).
I'm writing code that lets me create dialogs that have some shared elements (e.g. cancel button, ok button, title) but you can also embed your own template and controller to customise it more. When you create the dialog, you specify the template and controller you want in "template" and "controller" field of a dialog "object" which is passed on to the primary controller for handling dialogs. The dialog controller now needs to embed the template and instantiate the named controller to control the template elements.
The template code I'm trying to use for this part is this:
<ng-include ng-controller="dialog.controller" src="dialog.template">
If I remove the controller part, the template appears properly. The controller part generates this error:
"Argument 'dialog.controller' is not a function, got string"
How do I instantiate the controller?
Edit: As an example, with the Angular UI modal library you can do this to create a controller:
var modalInstance = $modal.open({
templateUrl: 'dialog_form.html',
controller: 'DialogFormController',
resolve: {
options: function() {
return dialog;
}
}
});
Where the controller field is the name of one of your controllers. How can I copy this functionality to specify my controller with a string instead of a function?
Angular Controllers are functions, and when you specify ng-controller, Angular will call that function and treat the return result of it as the controller object. That's why controller definitions are done as function-type constructors.
But when this happens there's an additional piece of magic - Angular has a controller Provider that maintains a registry of known controllers, for a variety of reasons. (For instance, it knows what injections they need.) You can't just define a global function and hope it gets called.
If you want to do this, see the ngController documentation which describes this option:
From https://docs.angularjs.org/api/ng/directive/ngController:
If the current $controllerProvider is configured to use globals (via
$controllerProvider.allowGlobals()), this may also be the name of a
globally accessible constructor function (not recommended).
You would use something like this if you wanted the functions supplied to you to be in a global variable, although as noted above it's not recommended.
ngController can also take an expression. In that case it will look for dialog to be a scope variable in the parent controller where this is used, so in there you would need something like:
$scope.dialog.controller = function() { /* ... */ };
This second technique is less useful if you want to make a generic library, but there are ways around it. For instance, you might have your developers create a dialog collection in $scope or $rootScope:
$rootScope.myDialogs['dialog1']['controller'] = function() { };
and then use this in your template like:
<ng-include ng-controller="myDialogs.dialog1.controller" src="myDialogs.dialog1.template">
Finally, you could implement your own ngInclude directive that just did both of those things together from a single argument ('dialog1'). AngularJS Directives give you incredible control over the templates and controllers used to run them.
I have a controller that has an object on its scope: $scope.objToTrack.
I have a directive that is inside a nested view that $watches for changes to that object.
It has isolate scope, but objToTrack is set as = so that it can be watched.
When I click the directive, it calls an expression that is a method on the controller which changes objToTrack.
Here's a plunker to illustrate my setup.
The problem is that objToTrack $watch callback isn't fired, although the object is changed.
If you switch between Test1 and Test2 states, changes made to objToTrack are visible. It's just that I don't understand why it doesn't work right away on click.
Thanks.
To answer question...if you bind your own event handlers to an element, and change angular scope within that event handler you need to call $apply so angular is made aware of the change and can run a digest
Example You have:
element.on('click',function(){
scope.onClick({number:RNG.int(200,300)});
});
Would need to be changed to:
element.on('click',function(){
scope.$apply(function(){
scope.onClick({number:RNG.int(200,300)});
});
});
It is a lot simpler if you use event directives already provided by angular. In this case you are writing considerable amount of extra code vs using ng-click. It also makes testing a lot easier when you stay within angular as much as possible
Also, if you want to pass an object into your directive you should not use curly braces.
In html, use obj-to-track="objToTrack", instead of obj-to-track="{{objToTrack}}".
Like this:
<div simple-directive obj-to-track="objToTrack" class="directive"></div>
And in directive.js: use '=' for bi-directional binding of the objToTrack.
Like this:
scope:{
objToTrack:'='
}
In your "test*.html" files, replace "on-click" by "ng-click".
"on-click" doesn't look in your current controller, "ng-click" does.
Is there a way to call a common controller everytime ng-view is changed? i.e i want a common controller to be called everytime a new $route is loaded.
If you have specified custom controllers for your different routes, then there's no way that I know of that you can also specify a common controller that always gets invoked, unless you use some kind of inheritance and always call a method in the base controller.
An alternative approach is to subscribe to the events the route service broadcasts.
Example:
function MyController($rootScope, [...]) {
$rootScope.$on('$routeChangeSuccess', function (current, previous) {
// ...
});
}
You have a list of available events and their parameters here.
I believe you also can add properties, methods etc. to $rootScope which you can use in bindings in your views thanks to how Angular's binding mechanism works. If it doesn't find it on the current scope, it checks its parent etc. up to the root scope.