AngularJS : Re-use controller functions and templates in different controllers - javascript

The title is a big ambigious, but this is the problem.
Assuming I am working in AngularJS and I have several views that largely need the same logic.
Code overview
app.js
$routeProvider
.when('/sheet/:sheetid/:screenid', {
templateUrl: 'views/sheet.html',
name: 'sheets',
controller: 'SheetCtrl'
});
index.html
<div class="container" ng-view></div>
The shared controller
Now this is the part where it all falls down in my mind. I need this controller to work on all 10 sheets. Right now I'm loading the data/model for the sheet through a service, which is working for now. The thing is though, that every sheetid shares a lot of code, but they can also differ from one another.
For instance:
sharedController.js
angular.module('App')
.controller('SheetCtrl', function ($scope, $routeParams, sheets) {
$scope.sheetid = $routeParams.sheetid;
/** GET THE SHEET */
//Gets the correct model from the sheets service (via promise)
$scope.sheetData = sheets.getSheet($scope.sheetId);
/** FACTSHEET SPECIFIC LOGIC */
//TODO: Figure out how to load specific factsheet controls in here
/* THE LOGIC FOR ALL THE SCREENS. WE NEED THIS AT ALL TIMES */
$scope.setScreen = function(index) {
...
};
//there are some more functions here that need to be shared
});
The view is a problem too
Every sheet has to be parsed in the same template. The problem is though that even these templates can differ tiny bits, and only from one div nested in this template. I would want to assign sheetID specific controller logic to the sheet-content, and also give the sheet-content it's own template/view.
<div class="sheet">
<div>
close
</div>
<div class="sheet-content">
<!-- Assume sheet specific controller code to target this div-->
</div>
</div>
Now what?
Not quite sure how to continue from here. It seems like I should be able to somehow dynamically assign controller logic for specific sheetID's to the sheet-content div and assign a template here. I'm really looking for the method to do this as clean and DRY as possible.
For instance, I could copy/paste the view and sharedController 10 times and assign them to every possible path, but I really do not want to repeat myself.
Another problem is that every factsheet should build his own model somehow. Right now I am doing it through a service with predefined data, but it would be cleanest if the specific controllers are handling that. The problem is when I put that logic in the specific controllers, the sharedControllers can't use that model anymore and won't build again.
If anyone could point me in the right direction I'd be very happy about that.

Partials loaded inside the parent inherit parent controller:
<div ng-controller="SheetCtrl" class="container" ng-view></div>

Here's what I eventually did. I included a view in the sheet-content according to a $scopevariable. I created 10 different views. In these 10 views I only changed the ng-controller element. All these views then ng-include another view which is shared across all 10 controllers.
I'm not totally happy about loading the data for the sheet in the sheet.js controller, but for now it seems the most DRY method of working. If I can think of a way to load this data in the corresponding sheet controller I'd rather do that.
sheet.js
This controls all of the sheets, contains shared functions and loads the data
$scope.template = {
url: '/views/sheets/sheet' + $scope.sheetid + '.html'
};
sheet.html
This view simply loads the template provided above, looking at the sheetid
<div ng-include="template.url">
sheet1.html (goes on to sheet10.html)
Simply assigns the right controller so I can seperate my code
<div ng-controller="Sheet1Ctrl">
<div ng-include="'views/sheets/sheet-wrapper.html'"></div>
</div><!--controller-->
sheet-wrapper.html
This view is shared across all sheets. Changing this view changes all the sheets
<div class="mpj-factsheet-dynamic"></div>

Related

Nested view controller and ng-click - AngularJs

I have 8 jade view that only one of them is loaded at the time and is filled by jquery into a div which has a controller.
Now, I have 2 question about these:
Does it necessary to define again the controller on top of my partial view(same controller with main controller) ?
All of these views has same ng-click. but after loading they doesn't work. However they work by jquery click event. Should I do any extra thing with them?
I had same problem with li element before, but I resolve it by getting help from ng-click not working from dynamically generated HTML by using compileData but I can't get result with button.
Code:
Main jade:
div(ng-controller="elementCtrl")
div#ddd(class="col-lg-7 col-md-5 col-sm-7")
Partial view sample:
div#spPartial()
div.col-lg-12.col-md-12.col-sm-12
span.col-lg-2.col-md-5.col-sm-5 Name
input#EnglishName(name="name" type="text" ng-model="elementModel.Name" value="#{Name}" class="col-lg-5 col-md-7 col-sm-5")
button(type="button" compile-Data name="btnSaveElement" ng-click="saveElement()") Save
Main part of controller:
//It loads the partial view - It works successfully
$http.post('/api/elements/getElementTypesPartial',
{
"ElementId": elementId,
"ProgramId": newVal,
"ElementTypeId": elementTypeId
})
.success(function (d2) {
$("#ddd").html(d2);
}
//It doesn't work at all
$scope.saveElement = function () {
alert();
alert($scope.elementId);
}
And one additional thing is that I put $scope.saveElement in root of controller scope. I don't have any idea about how angularJs manage $scope, So I see $scope.elementId in client code. Is it right or I should regenerate it($scope.saveElement) every time that partial is loaded?
Sorry I couldn't find any reference which describes these...
You should get rid of the jQuery loading and use an angular router which will load templates based on route configuration.
Since they are loaded by angular, it does all the compiling for you.
The router takes care of the ajax to get the templates automatically also.
Controllers also get defined in the routing config so you would be able to remove ng-controller from the templates
The change over shouldn't take long since setting up routing config is fairly easy to get it started
This would clear up the ng-click problems

Angularjs: is this the right place to use a directive?

I've recently switched from jQuery to Angularjs and I am in the process of re-coding some pagination logic for the links ("Next", "Previous", etc.) that were written in jQuery-style Javascript previously.
Each link has an ngIf condition (for example, the "Previous" link won't show if you're on page 1) plus an ngClick event, which essentially updates a scope variable called $scope.pagination.position that determines which results are displayed in the table.
My original code was something like this (simplified for clarity):
Template
<a ng-if="pagination.position > 0" ng-click="pagination.first()">First</a>
Controller
$scope.pagination = {
first: function() {
this.position = 0;
}
}
Then I learned more about directives, and how most DOM elements that aren't static HTML should be created using a directive. So I switched each link (since each has it's own display rules and behaviour on clicks) to its own directive, like so:
Template
<a pagination-first></a>
Directive
app.directive('paginationFirst', function() {
return {
link: function(scope,el,attr) {
scope.pagination.first = function() {
scope.pagination.position = 0;
}
},
replace: true,
template: '<a pagination-first ng-if="pagination.position > 0" ng-click="pagination.first()">First</a>'
}
});
I'll cut straight to the chase : am I doing directives wrong? All that's happened, from my perspective, is I've flipped from having logic in my template to having a template in my logic, and I've defined the click event function in the directive rather than in the controller.
Is this even an appropriate time to be using a directive?
I'd like to learn best practices, so I'd love to know if I've missed the point and if the original templated-based ngIf and controller function approach was fine, even with longer and more complex ngIf conditions than the one shown.
If I want to add specific behaviors to a dom or dom list then I normally create a directive. As per angular js perspective the dom manipulation should only be done through directive (For me it is the best place, sometime I have to disobey this due to my lack of knowledge ). I specially found directive use full while creating a widget. In one of my project there was a part where a section is dedicated to display an image and also upload the image. I just use the directive on the top div, with the help of link function I attached the event handlers to various child dom. And as my project doesnot require an isolated scope (as this widget was all used in a single project and the outer scope was under my control) . So it worked like a charm. I cerarted the directive once. And used that widget through rest of the project as it's behavior and design (of the widget ) was same through out the project. For the pagination widget you can create a directive. Take the directive attibutes value as the input of the pagination parameters. Like calling script, limit offset. Container identifier to update the content. Then you can solely concentrate on the pagianation behavior. But from my experience (as I am also not so experienced in angular js), sometimes it becomes a little hectic to develop a directive and and use that throughout the project. As in some places we need to modify the behavior of the directive. And for this it may breaks elsewhere. But I know as I learn more I will be more efficient to handle this kind of situation. Hope my experience will help you.

In AngularJS, how can I nest variable child directive(s) inside a parent directive?

Introduction
For the project I am working on, I am trying to tackle a particular problem in the 'angular way', however I think I must be missing something because no matter what I try I continue to reach brick wall.
The crux of this issue is I am dynamically loading data from a backend that describes different components that are visible to the user. That's not the issue itself, but rather the issue of the particular & proper 'angular' way to turn a list of 'models' describing the components into actually rendered HTML.
Problem
What I am trying to create is basically the following:
Start off with a parent directive that uses ng-repeat for a scoped list called "models", which contains zero or more "components":
<parent-directive ng-repeat="model in models" model="model"></parent-directive>
The ng-repeat directive creates N copies of that original directive with different 'model' arguments (for each object in the $scope.models array).
// this is just for demonstrative purposes, it obviously looks different in source
<parent-directive model="child1"></parent-directive>
<parent-directive model="child2"></parent-directive>
<parent-directive model="child3"></parent-directive>
issue! => The parentdirective gets transformed into a specific child directive depending on data (in this case, called 'type') contained within the javascript object:
<parent-directive model="..."></parent-directive>
turns into
<child-directive-one model="..."></child-directive-one>
or
<child-directive-two model="..."></child-directive-two>
dependent on what the value 'model.type' is.
The child directive then renders into it's own custom HTML (outside the scope of this problem) using data passed to it. If we continued the example from above, that HTML should render into the following (hopefully):
<child-directive-one model="child1"></child-directive-one>
<child-directive-one model="child2"></child-directive-one>
<child-directive-two model="child3"></child-directive-two>'
Followed by (and this is outside the scope of the issue but just to see it through to the end) each directive rendering into its own HTML:
<div>in childDirectiveOne, text is: This is text contained inside child1</div>
<div>in childDirectiveOne, text is: This is text contained inside child2</div>
<div>in childDirectiveTwo, text is: This is text contained inside child3</div>
Source
I've been trying lots of different variations of things to try and get it to work (involving the link function, using $compile, etc), but this source is provided with all of those attempts stripped out. Here's the source I've developed so far:
removed source (was filled with errors). Solution that Scott helped me out with is below:
Conclusion
Thanks for any advice in advance.
Update:
Solution exists here (thanks again to Scott).
I'm not sure exactly why you can't just have a single directive, however something like the following might work. Instead of repeating the parent directive you just pass in the models and have that directive repeat and create each of the child directives.
<parent-directive the-models="models"></parent-directive>
Parent directive template:
<div ng-repeat="model in models"....>
<child-directive ng-if="YOUR CONDITION"></child-directive>
<child-directive2 ng-if="YOUR CONDITION"></child-directive>
</div>

Views within non-Backbone templates and rerendering

I have an app that has a single page main.html, and a few major templates admin.html, user.html, etc. This app is not currently backbone, just some sections of it (slow migration).
In the admin page, I have BB views for users, etc. When a user selects a path to /admin/users, e.g. then a master template loader (non-BB) loads admin.html into a standard location in main.html, and then runs reset on the usersView:
// pseudocode
function() {
master.loadTemplate("admin.html").then(function(){usersView.reset();});
}
admin.html contains several elements, only one of which is the template for usersView. So it might look like:
<div id="admin-fragment">
<div id="admin-users">
<!-- template for usersView -->
</div>
<div id="admin-something else">
</div>
</div>
So when I load the script that contains usersView, it looks like:
UsersView = Backbone.View.extend({
el: "#admin-users"
});
All pretty good. Here is the issue.
Sometimes, a user might go somewhere else in this single page app, then go back to /admin/users. So the master template loader loads (and sometimes reloads, depending) the entire admin.html. But the usersView is already attached to the existing (now orphaned) #admin-users.
How do I resolve this? I see two ways, could use some ideas:
instead of usersView.reset() when I select /admin/users, create a new usersView:
// pseudocode
function() {
master.loadTemplate("admin.html").then(function(){usersView = new UsersView();});
}
My concern is the performance and management hit.
somehow tell the existing usersView to "reparent" / "reconnect", i.e. find the element anew.
Any better ideas?
See these posts on good patterns for rendering nested views in Backbone:
Assigning Backbone Subviews Made Even Cleaner
Rendering Views in Backbone.js Isn't Always Simple

ASP.net MVC - Views and jQuery Best Practices

I'm trying to figure out what the best practice is for using jQuery in an MVC app. Specifically, I would like to know what I should do so that I don't clutter all my views with individual document.ready statements.
As an example:
I have the following Views:
/Views/Shared/_Layout.cshtml
/Views/Home/Index.cshtml
/Views/Home/_Dialog.cshtml
/Views/Home/_AnotherDialog.cshtml
I have a controller action that will render the Home/Index View, which uses the Layout and renders two partial views (or editor templates, display templates, etc.). This one controller action has rendered 4 or more views. Each view is using some jquery document.ready code.
Currently, I have the code at the bottom of each view:
// In Index
<script type="text/javascript">
$(function() {
$('#tabs').tabs()
});
</script>
// In _Dialog
<script type="text/javascript">
$(function() {
$('#some-dialog').dialog( ... );
});
</script>
I know this isn't a very good practice because it is already getting unmanageable in my small project. What are some good practices to follow when I have tons of pages that all need some jQuery / javascript initialization code separated across dozens of views?
You could do something along the lines of what Telerik do with their javascript registrar. Basically, make this registrar available in your view model. At the simplest level, all it has to do is keep track of strings added to it:
public class JavascriptRegistrar
{
private StringBuilder jsBuilder_ = new StringBuilder();
public Add(string js)
{
builder.Append(js).Append('\n');
}
public string ToString()
{
return "<script type=\"text/javascript\">" + jsBuilder_.ToString() + "\n</script>";
}
}
Your partial views will then add to this when rendering:
<h1>In my view!</h1>
#Model.Registrar.Add("$function() { /* ... */ }")
Finally, at the bottom of your main view, when you're done:
#Model.Registrar.ToString()
Which will write out all the javascript it has collected during rendering.
If the initialisation is specific to a view and you know it definitely won't be used outside that view, for example some page specific behaviour, then just leave it in the view!
There is nothing wrong with having script tags in all your views, as long as you aren't replicating js between views. I think people tend to misunderstand 'separation of concerns' in this case and think that simply means 'keep different languages away from each other at all costs'...that is wrong, clearly if some page initialisation logic/behaviour is specific to a page, then the html and js intrinsically 'concern' each other, therefore moving the js into a separate file is not really 'good practice', if anything it makes your code more difficult to understand.
I personally like to open up a View, and be able to see all the js and css that is specific to that page as soon as I open it, makes it nice and readable. However, obviously if code needs to be shared then you need to bust it out your view and get in your scripts folder whwere it can be referenced by anything!
EDIT
In your example above I see in your Index view you initialise your tabs. This is fine as it is, however, if you added tabs somewhere else in the project then it might be better to create your tabs using a .tabs class rather than #tabs id, and then in an external js file initialise all your tabs at once by calling $('.tabs').

Categories