Sharing data from multiple instances of a directive - angularjs

I have a custom directive which is re-usable throughout the application. This directive uses ui-grid and I'm trying to determine the "angular way" of allowing access to the grid API from anywhere in the application.
If it was only a single instance, I'd use a service to share data across controllers:
var attachments = angular.module('attachments', ['ui.grid']);
// this would be accessible from any of my controllers
attachments.factory('Attachments', function() {
return {};
});
attachments.directive('attachments', function() {
return {
restrict: 'E',
templateUrl: 'attachments.html', // template has a ui-grid, among other elements
controller: ['$scope', 'Attachments', function($scope, Attachments) {
$scope.gridOptions = {
// ui-grid code here
onRegisterApi: function( gridApi ) {
Attachments.grid = gridApi;
}
};
}]
};
});
However, there could be multiple instances of the directive
For example, there might be a primary instance of this directive and one inside a modal, or one in a sidebar, one in a modal, etc.
I suppose I could add property namespaces to that service...
Attachments = {
libraryGrid: // ...
someModalGrid: // ...
}
etc...
I'd prefer to avoid making a service for each possible instance, i.e.:
attachments.factory('SomeModalAttachments', function() {
return {};
});
While it would work it feels inefficient. However, both choices are a lot better than digging into the modal scope and finding child scopes with the necessary API.
Is there any other method I haven't considered?

To me it depends on your usage model.
If you're going to have multiple of these and other bits of the application are going to access them, then that means one of a few things:
The access is actually initiated from the grid. So you have perhaps many list pages, and the currently active list page is the one you want to deal with. So I'd have the grid register with all the things it wants to talk to, and deregister when it closes again. The other things would all be services (singletons).
There are a specified number of these grids, and you talk to them by name - so you want to interact with the list-page-grid, or the modal-grid or whatever. So you have each grid register with somewhere central (maybe a service that everything else talks to).
The grids are subsidiary to something. So a page includes the grid directive, and then that page wants to talk to that grid. You could pass an object into the directive, then have the grid register itself on that object. So you call the directive with "myGridCommunicationObject = {}", and then the grid does "$scope.myGridCommunicationObject.gridApi = gridApi". This doesn't let other bits of the application talk to the grid, but if really you only want whatever created the grid to talk to it, then it works well.
You could broadcast. So if you don't really care which grid you talk to, you just want to talk to any grid that's currently visible (say you're resizing them or something) you could just broadcast an event to them, and all your grid directives could listen for that event. Taking this one step further, you could broadcast and include a grid id or name in the parameter, and then have each grid check whether it's me before taking action.
Having said all those options, there's something about what you're doing that has a bit of a code smell to it. Really, arbitrary bits of the application shouldn't want to talk directly to the grid Api, they should be communicating with methods on the controller that holds the grid. Perhaps some examples of what you want to do would help, but it feels to me like the model should be one of the grid registering to use other services (e.g. resize notifications), or of the controller that owns the grid interacting with the grid, and other things interacting with that controller.

Related

ag-grid + angularJS header and cells editable by the user

I just start using the ag-grid library with angularJS to display some tabular data in my application. The thing now is that I want the user to be able to alter the header value of each column like as he/she can edit the cells within a column when we enable the editable attribute. While searching for a solution I realized that there is no default way to turn the header into an editable element. So I was wondering which are my alternatives.
One thought of mine was to add to each header an edit button and when clicked a modal window to pop up with the corresponding input fields of the header and its column's cell values. In this case, the editable attribute is not going to be used because the edit of the cells will be conducted within the modal. Something like that but instead for the row, the button should be in each header.
So which are the ways that I can achieve something like that? I read about header template but couldn't really found any straightforward way to approach it.
Any ideas/hints or other alternatives are welcome.
Make a service which have an array as DataSet of your grid, and provide it with handlers for the events of a CRUD in the same service. Inject the service into the controller of your modal and view of the grid. After that, call the object gridDataSetadd,remove,update who is in the service, then $digest do the rest of the work once DataSet will be modified.
UPDATE, answering the comment
Inject your service into your controller:
For .ts
static $inject = ['$scope','YourService'];
constructor($scope, userService: YourService) {
this.userService.getSomething();
}
For .js
.controller('YourCtrl', ['$scope', 'YourService', function ($scope, YourService) {
$scope.someVar = YourService.someVar;
}])

Switching the data, using the same Controller AngularJS

I was wondering how to change the data, but keep the same controller in my angular app. Basically I will have a list of activities (restaurants, parks etc...) when I click on one of these activities, The view will display all the restaurant, and same thing for the parks. I know how to do that, but I would need to create a park_ctrl and a restaurant_ctrl, and since the data will be formatted the exact same way. I just wanted to know if I could use only one controller and just change the data that it receives when I click on those buttons.
I hope my question makes sense.
logic around retrieving data should be the responsibility of services, so I guess you'd just call a different service in the different cases, from the same controller
I think it's not a really good idea, but opinion based.
You can make a function :
function($scope){
$scope.changePage = function (type) {
if(type==="park"){
$scope.parks = asynLoadFunctionToGetParks();
}else{
if(type === "restaurants"){
/* same as below */
}
}
};
}
And changing the type in your view with :
<button ng-click="changePage('parks')">Parks</button>
<button ng-click="changePage('restaurants')">Restaurants</button>
<div ng-if="type==='park'">
{{parks}}
</div>
<div ng-if="type==='restaurants'">
{{restaurants}}
</div>
I think the issue here is that most of the Angular examples available are of the "hello world" variety and so they show retrieving data directly from the Controller. The problem is that AngularJS out of the box doesn't really have a business logic layer itself, and I think most people who have added such a layer are too busy to be putting up examples.
The way I'd do this is to create a "master" service that can get all of the different data types either up front in the Run block or lazily as the user navigates the app, depending on your needs. Then I'd supply a reference to the applicable sub-collection in the route resolution (resolve property) or the isolate scope in the case of a directive.
Alternatively, the controller can ask for the data by calling masterService.getCollection($scope.collectionName) or something like that, but if you do that you run into the issue that masterService may not yet have that particular collection yet and then you have to clutter up your controller with all the promise resolution stuff as if it were a Controller's responsibility to handle that.
You could avoid that by binding to masterService.collections[$scope.collectionName] in the View, which would leave the Controller only exposing the collection on the $scope or controllerAs variable and the masterService still responsible for retrieving the data and making it available.
Yes you can. Just use different service and a common variable in the scope.
if (something) {
$scope.data = restaurantsService.get();
} else {
$scope.data = parksService.get();
}

(How) can a controller be active when its view is not visible?

If I understand correctly, a controller is generated when its view becomes active?
Meaning that if the view is not visible, the controller doesn't exist/isn't active?
Is there any way that I can make the controller active "in the background"?
I have an app which has two main tabs, one for vehicle tracking & the other for something else. Each of those tabs has several sub-tabs (suing UI-Router); in the case of the vehicle tracking, I have one tab for a google map & another to show a grid with street addresses.
Here's a pic of the desktop app which I am trying to re-implement in Angular:
Now, I am an Angular beginner, so maybe my design idea is flawed, but:
Both the map and the vehicle locations tab use the identical data pulled from a server, which is a list of GPS lat/long & timestamp.
It seemed to me that it would be wrong to have each of those tabs (controllers) fetching the same data, duplicating code.
So, I wanted to have a controller for the "Vehicle locations" tab, which would get the data and feed it to a service which the two map/Vehicle locations sub-tabs (controllers) could $watch.
Even if I were not looking at that "parent view" ("Vehicle locations", but at another ("Vehicle reports" or "Employee reports"), I would this data downloading & view updating to continue, so that when the user clicks the "Vehicle locations" tab, the information is already there & the user doesn't have to wait while the app goes to the server to fetch data & draw its view.
Question: can Angular work that way, or am I asking it to do something which is un-angular-ish? If so, what is the correct way to implement this? Should I duplicate the $http.get code and should I have the controllers fetching & updating their own data on $scope.$on('$viewContentLoaded'?
If you need a central point of access for data in your AngularJS app, the best way to achieve it is to encapsulate it in a provider (either a service or a factory). Fetch the data in your provider and implement getters, so that you can inject it in your controllers and ask for the data without duplicating code and requests.
yourApp.service('serviceName', [ '$http',
function($http) {
this.getData = function() {
return $http.get(...);
}
}
]);
yourApp.controller('controllerA', ['serviceName',
function(serviceName) {
serviceName.getData().then(function(data){
$scope.data = data;
});
}
]);
//the same for other controllers
There are some points you can consider for this app:
You can create a factory for data you intend to share and reuse. So maybe create a http factory to get the data from the server and then inject it to whichever controllers you need to. Also you can resolve incoming data with "resolved" in angularjs (an option in routes) to make sure it's available before the page renders.
If you are passing in data within controller to http.get then, inject the factory and use promises with the http method.

Best practices for initializing and deconstructing controllers

When building an application what is the best way to setup your controllers?
I understand that routing, event listeners, and most all interactivity should be managed by the controllers, but my main controller is starting to grow out of control and I'm not sure how to best separate my logic into separate controllers without keeping them all "running" all the time...
It's okay to have them all loaded at app start-up, even with hundreds thousands of controllers. It's the view rendering that takes time.
Make sure your app is minimized and concatenated though, using sencha cmd.
In a test, I created 1000 simple controllers, like this:
Ext.define('app.controller.ControllerN', {
extend: 'Ext.app.Controller',
init: function(application) {
this.control({
buttonN1: {
click: function() {
}
},
buttonN2: {
click:function(){}
},
... 20 listeners
});
}
});
I concatenated them into one file, and loaded them in app.js like this:
Ext.application({
name:'app',
controllers:[
'Controller0',
'Controller1'
... 1000 controllers
],
launch:function(){}
}
It took ONE second from browser refresh (Chrome) until the last controller's init method was called.
I had similar problem so I divided controllers on the basis of business functionality it will support, e.g. userController does all the user related operations like login, logout, update etc whereas cartController does all the operations related to shopping cart like add to cart, apply coupons, payments etc. Since a single view can have many functionalities related to different areas of app so you can add refs to this view in multiple controllers and listen to only relevant events in corresponding controller.

When using Marionette.js when should I call vent.trigger('app:show', Controller.layout)?

Of course the answer is "Use it when you want to show the app". Ok that is fair enough. But what about subviews? I am using Ted's example: https://github.com/t2k/backbone.marionette-RequireJS. That example only has one controller setup. I have six controllers which I copied the libraryController in Ted's example. Each example has the following code:
var _initializeLayout = function() {
console.log('initialize Start Layout...');
Controller.layout = new Layout();
Controller.layout.on("show", function() {
vent.trigger("startLayout:rendered");
});
vent.trigger('app:show', Controller.layout); <!-- is this needed for each?
};
So I have that code in each of my controllers. The StartLayout has two regions that have their own views that are the entry points to the InspectorController and the PlayerController.
Each of those controllers has:
vent.trigger('app:show', Controller.layout);
So it would seem to me that I may be calling 'app:show' more than needed. Once for every Controller that needs initializing.
Is this necessary? I can understand perhaps calling that when I'm dealing with direct child views of the app but if I'm deep into PlayerController and the app view isn't visible it seems like overkill.
Thanks,
Andrew
Try not to think of "calling" app:show. It's not a function, it's an event. An event can have an arbitrary number of subscriptions listening for it. In the case of this application, there is only one listener on that event:
vent.on('app:show', function(appView) {
app.content.show(appView);
});
In this case, it's telling the content region to display whatever view is included in the event as appView. So, if you want to replace the content region with your own view, you should trigger app:show with a parameter of whatever view you want the content region to display.
content is bound to a DOM element, and whenever you call content.show(someView), the contents of that DOM element will be replaced by whatever is generated by someView.render().el.
I would suggest reading up on Layouts, Regions, and Events.
Hope this helps.

Resources