I have some fairly complicated logic in a bootstrap dialog which I've moved into its own controller for isolation.
There are times I want to launch the dialog or call a function in the controller based on some logic that occurs elsewhere in the app - in another controller, or a service. I've achieved this by adding an id to the ng-controller element then looking up the element by that id, and calling things off the .scope() of that controller. Essentially this:
In html:
<div id="modalController" ng-controller="modalController">
And in another service or controller:
angular.element("#modalController").scope().somefunction()
This seems pretty weird that I can't just get a controller by name. Is there a way to do this?
Create a service and bind the model to data maintained in that service. Make a change to the model within the service and it's made everywhere.
You could also create a service that provides a pubsub interface to the changes you need to make.
Yet another way to do it would be to have a single model representing the state of your system and modify that. Attach the relevant parts of that model to the scopes of each widget as necessary and you have a communication device built in.
It sounds like you are making a change in one place that should cause a change in another place. If that's the case, I'd argue having a service that updates all parts of the model correctly is the best way to go. Always imagine what you'd do if you added another widget that hangs off this functionality.
Related
I'm trying to update the DOM based on a click event, by using a service.
Specifically, I'm trying to use bootstrap's alerts to alert the user when they click on a particular button on the page. I'd like to be able to call notify.createNotification("Saved successfully") for instance, to tell the user that they have saved successfully.
I'm using ui-router and have managed to abstract the notification to a (isolate scope) directive and the accompanying template. The notification currently shows, updated, at the head of my application (all other views inherit this view) upon $scope.createNotification() (from within the click event). This works because the child views inherit the $scope property. Clearly though this pollution and abuse of the inheritance of the $scope is not ideal, hence I'd like to move it all into a service.
I've got as far as trying to use a factory to update the notification object, which contains state information for the alert, i.e. display:true/false, text etc. The trouble with the factory is it just returns where it's called from, I need to be able to update the parent.
...I feel I've done 9/10ths of the work on this, but that last 1/10th is really puzzling me.
Any help would be greatly appreciated.
Note: Guess I'm also looking for a 'best practices' here too, I mean $scope pollution works, but it's far from ideal. Thanks
This could be solved using pub/sub approach.
Create NotificationService that is used to send notifications. For callers it would look like NotificationService.alert({text: '..', ...})
Create <notification-area> component that subscribes to NotificationSerivce and displays notifications sent from anywhere.
NotificationService itself should implement pub/sub interface. Use any implementation of EventEmitter (like this one) or even angular.element to provide on(), off(), trigger() methods.
I have a problem I haven't come across before, and I'm completely lost as to how to solve it.
I have a container which is used to display success and error messages across a wide variety of views. This container is defined by an Angular directive.
The problem is that I would like to have a service or something similar which I can inject into my controllers. I could then use methods on this service to make the "alert box" appear and/or change in content.
How do I go about achieving this or a similarly DRY setup in Angular?
Three perfectly good methods:
Use angular's event system, rootScope.$broadcast or scope.$emit and scope.$on as listener, see the documentation here: https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$emit
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$on
inject the service into the "alert box" directive, let it register itself somehow with the service, that way the service knows of its existence and can control the directive's controller, or its scope, or the element directly, or whatever logic you wish to use.
Use a container as parent, that has the orchestrating logic. the child directive's can require the container controller, and call functions etc. on the container, one child directive is then able to trigger another child through this orchestrating parent container.
EDIT:
My point wasn't to show you how to do it in the most generic and cleanest way, because I don't know your exact situation, you should always consider how generic and abstracted you want your functionality.
Here is a very very simple example to showcase that it's not about finding some complex system or pattern, but finding something that works well for you
method 2 example:
angular.module('myApp').service( 'myService', myService );
function myService(){
this.alertBoxes = {};
this.registerAlertBox = function(name, handle){
this.alertBoxes[ name ] = handle;
}
this.toggleAlertBox = function(byName){
this.alertBoxes[ byName ]();
}
}
I personally would use method3 from the looks of what kind of functionality you're looking for, method 2 could be a generic pubsub service using rootScope.$broadcast and scope.$on, or not even bothering with those and doing it like a very straight forward pubsub.
method 1 really is the simplest way, but obviously would benefit from being abstracted away into a service, where you inject the service everytime you need a very specific pubsub event.
For an example of a pub/sub pattern: Pub/Sub design pattern angularjs service
I have an app with two controllers where I want a change in one controller to affect (different) data stored in the other. tl;dr when I remove a category from the first controller, I want to edit any items in the second controller with that category, so that they will now be category-less.
As far as I can tell what I want is to use a service, but I feel it would be simpler if there were a way for me to simply edit the data inside the controller scope. So my questions are:
Is there a way to simply edit controller data or call controller methods from a service?
Otherwise, is it reasonable to store the latter controller's data in the service, even though the former controller only needs access to change it? How do I reference this data for the purpose of doing ng-repeats?
Edit: to clarify the data is a set of json objects which contain data for each category and each item, and the web page contains ng-repeats to go through and list each of them. I have a number of functions which edit both lists of data, and I want changes to one list to make changes in the other.
Your idea was correct, you should put all your business logic, including data that needs to be consistent between different parts of your application, into services. controllers should only manage the view and connect the data to it.
Keep in mind that services are Singletons - there is always only one instance of each service, holding your data.
To answer your question: I would argue that storing data in a service instead of a controller is always reasonable when it works (aka when the data is not specific to one of multiple views, but consistent throughout the current application state), and giving access to that data to manipulate it is perfectly fine - even better would be to put the manipulation logic into the service itself (or another service only for that) and to just let the controller connect to a call invoking that.
There is an article by Todd Motto on that topic (thin controllers)
I think it will be better use events for this purpose. In your first controller you can published the event on category deletion like below.
$scope.deleteCategory = function (category) {
$rootScope.$broadcast("categoryDeleted", category);
}
Then you can observe this event in any controller like below in second controller you can listen categoryDeleted event.
$scope.$on("categoryDeleted", function (event, category) {
// do whatever you want
});
Do not call controller directly from the service, this is a bad practice, not only in AngularJS, but in most languages frameworks.
The problem you have described ("a change in one controller to affect (different) data stored in the other") is a problem of communication between components. You can solve this issue with events, thus there is no need to move data from the second controller to the service.
Let's consider some example:
http://jsfiddle.net/simpulton/XqDxG/
When you click on the LOG button the this.broadcastItem() is invoked, and the 'handleBroadcast' event is broadcasted.
Other constrollers, controllerOne and controllerTwo, handle this event:
$scope.$on('handleBroadcast'
and do the things they want to do.
So, in yor case, you can introduce the 'categoryRemoved' event, and broadcast this event in the first controller. Once the event is broadcasted, your second controller handle it and edit its items.
Also you should pass the removed category in the event (instead of 'msg' in the example), so that second controller has to aware which exactly category has been removed.
In way you want to do that, $rootScope can be used (shared across controllers - modyfing in one, accessing in another), but its generally bad idea. Im not sure if I get it right but its typical situation when you actually need service with controlling some external data.. Its some static json you want to modify? Can you specify it more clearly ? :)
I am using ui-select to create a complex page with multuple views. For those that don't know each view is loaded via ajax when a user selects a tab (or in my case a dropdown). Views traditionally have their own controller and html templet. Views inherrit the scope of the parent page they are on, but have their own child scope.
In my case some of our views have no controller at all. They are so simple that we only have html pages for them. The problem is that I would like them to be able to toggle a boolean on a configuration object on the parent page's scope, to turn off on on some control fields on the parent page. Everything already exists, multiple pages use these views, I simply want to expand them to toggle something on the parent.
I could do this in three ways. I could in my ui-router use the resolve method to explicitly toggle the field I want when the view is loaded, but then I have to do this in each ui-router for each page explicitly; no cool code-reuse.
I could have my ui-router load a controller, where currently I have no controller, and the controller could do this. However, this means manually adding a controller that is mostly unneeded everywhere I use the view, and again doesn't feel like code reuse.
Or I could use ng-init to have my view's explicitly set the value I want at load time. To me this feels cleanest and easiest, since I don't already have a controller.
However, ng-init is frowned on, and should only be used for aliasing supposedly. Is there a cleaner approach then using the ng-init? is it really bad to have a view without a controller (to be more exact they have the parent controller still, but that isn't reloaded when a new view is opened).
The fact that you don't specify controller for the route doesn't change anything. The view has its own scope, so it can be manipulated with either route controller or with directives that don't have their own scope (e.g. ngInit).
The objective of well-known remark on ngInit
The only appropriate use of ngInit is for aliasing special properties
of ngRepeat, as seen in the demo below. Besides this case, you should
use controllers rather than ngInit to initialize values on a scope
is to keep you from 'html programming'. And it doesn't have too much sense because the separation of concerns is not among Angular's virtues. Angular's habit of evaluating the code from html attributes and the way how it is used by core directives prompt you to defy it even further.
Just dont init that variables at all. ng-show="smth", ng-if="smth" works ok if 'smth' is not defined and later u change it using ng-click="smth = !smth".
I have a SPA that pulls configuration data down from a server and generates a form that a user can proceed to fill out.
I want to be able to make this SPA testable outside of the browser, which would imply that the views and their implementations are not being tested.
Currently, the structure of a form is like so:
form
page 1
firstName
lastName
page 2
email
There is a form model which holds the data of the form and its configuration, and then there is a controller and a view. The controller attaches data from the model directly into the view, and also manipulates the view based on events. The view also has logic for creating and controlling certain aspects, such as inline scrollers (iScroll).
As you can probably see, this makes it impossible to test the code without the browser because the code is so tightly coupled to the view.
I think the way to fix the problem would be to have a controller, a controller model, a view, and then a concrete class that joins them together while providing the form configuration to the controller. But this raises some issues, specifically, that I would then need a factory for creating a controller for the form, pages, and fields, and then something that creates the concrete binder between the view and the controller, and then something that joins them all together in the correct way.
I feel like there has to be a better way to do this.
Any guidance would be greatly appreciated.
I think what you need is AngularJS. There is a template you can clone with git. It has testable capability right out of the box. I don't think there is a need to reinvent the wheel since there is already a well known framework that has this.