I am trying to introduce myself into angularjs.
Although i have worked through the tutorial and watched the basic building videos, i am still struggling with the behaviour and architecture of a more-or-less large scale application.
My application has a menubar that includes an add-button. If the user clicks the button, i want a dialog to pop-up. That dialog is not part of the menu:
<!-- The menu -->
<header class="mod modHeader" ng-controller="HeaderCtrl">
<div class="modHeader__addProject" ng-click="openAddDialog()">
<i class="icon-plus icon-2x"></i>
</div>
</header>
<!-- the dialog -->
<div class="modNewProject" ng-show="properties.AddDialogVisibility" ng-controller="HeaderCtrl">
<!-- content stripped out -->
</div>
My intention was to create a properties object inside the scope of my HeaderCtrl controller, then change a boolean value on click of the said button.
// HeaderCtrl
function HeaderCtrl($scope){
$scope.properties = {
"AddDialogVisibility": false
};
$scope.openAddDialog = function () {
$scope.properties.AddDialogVisibility = true;
};
}
Now, there are multiple issues and questions:
I have to apply HeaderCtrl to my dialog in order to get access to the properties object. This is nasty to me, HeaderCtrl should control only my header module, shouldn't it?
The dialog won't show up on click. I found out that this is because the property gets checked only once, on page load, and that i would have to use a function. What is a proper way to achieve my goal?
Conclusion:
I would say that i can summarize my question to:
I use a Controller for each section of my page. How do they communicate?
In the sample code you provided, two HeaderCtrls will be created. Each use of ng-controller will create a controller.
To share data in Angular, use a service. Inject that service where needed – into controllers, directives, etc.
When designing an Angular app, try to think in terms of models (which are often contained in services, and those services then expose model APIs to the rest of the application) and views. A controller is just the glue that allows us to project the relevant parts of our models into a view.
Dialogs are a special/unique case, since they don't take up a specific place in the rest of the application. Listen to a few minutes of Misko regarding this subject.
I would also recommend looking at how the Angular-UI team implemented dialogs: http://angular-ui.github.io/bootstrap/#dialog
angular structure
app->
assets->
css -> all css file
js -> all js file
partials-> all html files
vender -> third party file (like angular.js , jquery.js)
router.js
services.js
filter.js
directives.js
controllers.js
index.html
Related
I am trying to make a single page app where I want to load screens (partial views) via ajax. The problem is that the user want's to open the same screen multiple times (in tabs). I tried to load the controller code on initial load and loaded my screens (partial views) via AJAX. But the controller is already executed by the time the screen (partial view) is loaded.
I want to know how to make controller run after the partial view is loaded?
Or better yet, how to tackle this situation where i want to load the same view multiple time with different data/state?
I found a solution to the problem I was facing. I am not sure if it is the best way to move forward but given the limited knowledge I have on AngularJS so far, this solution is my best bet. If anyone can come up with a better solution, I am all ears.
Instead of registering controller up front, I am registering the controller when the view is requested.
Also, I am appending a new div to the container where I want the view to appear. There is some jQuery on the page that hides other views and only shows the current view.
Step 1
Assigned $controllerProvider.register to a variable on App Scope
var myApp = angular.module("myApp", []);
angular.module("myApp").config(function ($controllerProvider) {
myApp.registerCtrl = $controllerProvider.register;
});
Step 2
In the function where I have to load the view dynamically, I did the following
$("#viewContainer").append($compile('<div ng-include="pageUrl"></div>')($scope));
The above line adds a div with ng-include directive. It also compiles the directive. The pageUrl is where I am keeping the url to the view I want to include.
Step 3
In the view file I have to load, I have include JavaScript file that holds my controller. The rest of the file contains simple HTML with directives. In the JS file, instead of creating the controller, I am registering the controller like
angular.module('myApp').registerCtrl("MyController", MyController);
I hope this is helpful to others.
The problem I am having is I need the root element as an anchor tag, then need a div under the anchor tag. They will both be using the same angular controller which belongs to the same app. The databind on vm.Open works find inside the anchor tag, but it is not working inside the div tag. How can I have the div tag also bootstrap as 'app' with the controller 'ordercontrol'?
Right now I have :
My HTML
<a data-ng-app="app" data-ng-controller="ordercontrol as vm" href="#" data-ng-click="vm.Open = !vm.Open">{{vm.Open}}</a>
<div data-ng-app="app" data-ng-controller="ordercontrol as vm" id="QuickOrderDiv">
<div class="row">
{{vm.Open}} //showing as '{{vm.Open}}' inside page
</div>
</div>
Per AngularJS documentation, only one application can be bootstrapped per HTML document. There is probably not a good reason for you to try and declare and use multiple apps in one document. Depending on your intention, there are a few ways to proceed.
First, remember: assigning a controller to an element (using the ng-controller directive) creates a new scope that inherits from the parent. All elements, directives, and additional controllers used within that scope can use that controller to share functionality. So in essence, a controller is used to centralize models and application logic that is specific to an application and usually to a view (think of any functional task in your application, such as an order form or log in page; those are serviced by controllers).
If you want to reuse a behavior multiple times throughout your application, and that behavior is not specific to a particular view in your application (think of a component, like a date picker, or perhaps a shopping cart status icon/link) you may wish to encapsulate your logic in a directive.
Now, there is some overlap (for example, directives can have a scope or controller of their own), and it may be confusing when to use one or the other. As I mentioned above, controllers are primarily intended to contain business logic for a view in your application. Directives are more orthogonal, encapsulated templates and logic that a) you can easily reuse across the views in your application, and b) extend existing HTML elements with richer mark-up and programmed behaviors. You can use a service (which is a single-instance object) to coordinate data between controllers and directives.
Another common issue new developers struggle with is maintaining or inheriting different states independently, considering that you can only have one ng-view element per document. In that case, consider ui-router.
My wild guess for your case, you may want some sort of QuickOrder directive that binds to a value on OrderController to determine whether or not it should display, and contains additional template mark-up for displaying the order or whatever and the logic to manage it.
I'm working on a legacy web application (well, I say legacy, it's from about a year ago and programmed in Knockout and JQuery), and I'd like to program a new interface in Angular, with a view to replacing all other portions of the application with Angular as we go on as this is a very complicated user interface that isn't horrible enough to warrant a complete rewrite.
I would like to integrate Angular code with the existing codebase, and for my first controller I'd like to program a pop-up box which allows the user to modify some data. I created a div with a data-ng-controller of "MyController" and it all works well, but I don't seem to be able to create many of them.
If we have multiple controllers, i.e:
<div data-ng-controller="MyController">
</div>
<div data-ng-controller="MyController">
</div>
Both instances of MyController would have their own scope which is definitely want, and it works totally fine for my app this way. But unfortunately, my system works on cloning a div for every instance of the pop-up box:
<div data-ng-controller="MyController" id="myWindow">
</div>
<script>
function showWindow()
{
var dialog = $("myWindow").clone();
dialog.show();
)
</script>
When showWIndow() is called, the reference to MyController seems to travel along with it, so I don't get two instance of MyController.
I can't seem to find how to create multiple instances of the same controller and attach it though.
For example, I thought this might work:
<div id="myWindow">
</div>
<script>
function showWindow()
{
var dialog = $("myWindow").clone().setupDialog();
dialog.attr("data-ng-controller", "MyController");
dialog.show();
)
</script>
But unfortunately, the breakpoint in MyController never gets hit. I suspected that this might be due to the order of attaching, but reversing the call to attr() and show had no effect:
var dialog = $("myWindow").clone().setupDialog();
dialog.show();
dialog.attr("data-ng-controller", "MyController");
I assume that there's some method in the angular library somewhere that allows me to instantiate a controller and attach it to an element. About an hour of googling has yielded nothing but I can't believe that something like this doesn't exist somewhere.
The HTML must be compiled to make angular directives work.
Usually the compile steps are done by angular behind the scenes (ng-app, bootstrap). Ng-repeat does it for example when creating new templates throug iterations.
But here, angularJS doesn't "know" you have DOM nodes to compile and you must do it manually.
You must learn about compile on angular, here are the docs:
reference api for $compile (the last exemple on this page is the one that helped me the most)
reference guide for compiling on angular
Here's what you might have to do (not sure because I haven't a deep knowledge on this):
create a scope for your duplicated widget
compile the template (where angular look dom markup for directives and expressions)
link the compiled template to the created scope
I think I've found it, but I don't know if this is necessarily the correct way to do it.
I firstly removed the data-ng-app property from my <body> element. I then applied the data-ng-controller programatically and called angular.bootstrap to specify the module I wanted to link to the element:
function showWindow()
{
var dialog = $("myWindow")
.clone()
.attr("data-ng-controller", "MyController")
.setupDialog();
angular.bootstrap(dialog, ["MyModule"]);
dialog.show();
)
It certainly works for me, although I'm not sure if there are side-effects to doing this that won't be apparent until later.
At the moment I have an app that has a sidebar, and the sidebar loads different html templates using ng-include based on what operation the user chooses to do. It's a map related app, so for example, if the user selects the 'Add Leg' button it will load the add_leg.html template into the sidebar using ng-include:
// The Javascript code:
$scope.addLeg = function() {
$scope.sidebar = true;
$scope.sidebar_template = '/partials/legs/add_leg.html';
}
// Simplified example of HTML:
<html>
<div ng-app="MyApp" ng-controller="MainCtrl">
<a ng-click="addLeg()">Add Leg</a>
<a ng-click="addRoute()">Add Route</a>
<a ng-click="editLeg()">Edit Leg</a>
<a ng-click="editRoute()">Edit Route</a>
...
<div id="sidebar" ng-class="{'sidebar-hidden': sidebar == false}">
<div ng-include src="sidebar_template"></div>
</div>
<google-map></google-map>
</div>
This is all well and good and works as desired, but at the moment my app is only using one controller (MainCtrl in js/controllers.js) and it's starting to get really cluttered. I've got a lot of methods now because the apps functionality is expanding. I'd like to split my controller up into multiple controllers whilst retaining this sidebar functionality.
I want to have MainCtrl as the main controller that controls the loading of the sidebar template (by changing the sidebar_template variable that points to the file destination), and I want it to control some of the global map related methods (like fetching suburb names from coordinates, etc).
I've tried to split it like so:
controllers/js/main.js - MainCtrl
controllers/js/legs.js - LegCtrl
controllers/js/routes.js - RouteCtrl
I want the LegCtrl and RouteCtrl to inherit the MainCtrl so I can access its scope and methods, that's all fine. But now the problem is how do I dynamically load the controller onto the sidebar div based on what functionality is required. Originally all of my methods were in MainCtrl, and that's on the wrapper div that surrounds the sidebar div (see above again for an example), so it wasn't a problem.
For example, say I press the 'Add Leg' button, it's going to need to call addLeg in LegCtrl, but LegCtrl isn't loaded on the app, so it doesn't have access to the method. I could keep addLeg() inside the MainCtrl, and have it change the sidebar_template variable to load the template, but nothing in the template will work because it is calling methods from inside the LegCtrl now.
I need to somehow dynamically load the controller on the sidebar's ng-include div, something like this perhaps:
// MainCtrl
$scope.addLeg = function() {
$scope.required_controller = LegCtrl;
$scope.sidebar = true;
$scope.sidebar_template = '/partials/legs/add_leg.html';
LegCtrl.addLeg();
}
<div id="sidebar" ng-class="{'sidebar-hidden': sidebar == false}">
<div ng-include src="sidebar_template" ng-controller="{{required_controller}}"></div>
</div>
In the non-working example above you can see a possible solution I've thought of, but I need LegCtrlto be the actual controller function, not an object (for ng-controller to work). I also need some way to call addLeg on the LegCtrl from the MainCtrl.addLeg (perhaps using broadcast?).
If anyone can point me in the right direction that'd be great. Sorry for the huge post, it needed a bit of explaining to make it coherent. Hopefully it makes sense. Thanks.
Update: I think I've found a solution using a service to act as the navigation control (it will load the relevant templates and broadcast an event to the new controller being dynamically loaded to tell it what function to execute):
http://plnkr.co/edit/Tjyn1OiVvToNntPBoH58?p=preview
Just trying to test this idea out now, but the broadcast .on doesn't work. I think it's because the broadcast fires before the template loads. How can I fix this? Is this a good solution for what I'm doing?
If i have understood you correctly what you can try would be to create a template view specifically to create a new leg.
From the main controller implement some logic to show the template
$scope.addLeg=function() {
$scope.showAddLeg=true;
}
The AddLeg template view would load the controller and hence provide a mechanism to actually add new leg. The template would look like
<script type="text/ng-template" class="template" id="addLegTemplate">
<div ng-controller='LegsController'>
<!--html for adding a new leg-->
</div>
</script>
You can include this template inside you main html using ng-if + ng-include.
<div ng-if='showAddLeg'><div ng-include='addLegTemplate'/></div>
Basically you can create multiple view and bind to same controller (but instance would differ). In this case the LegsController can be binded to multiple views to support the complete functionality.
I like this data driven scenario, just manipulate the rows in the templates:
$scope.templates = [
{ name: 'include1.html', url: 'partials/include1.html' }
];
$scope.addTemplate = function(name, url) {
$scope.templates.push({ name: name, url: url });
};
and the markup:
<div ng-repeat="template in templates" ng-include="template.url"></div>
To add or remove views, just modify the templates et voila! If you need more controller code, then you can include a link to the script in the include. I guess there may be complications with binding to data in the partial view itself, but $compile should resolve those.
I've forked your plunkr demo into one that I believe does what you're looking for: http://plnkr.co/edit/whsjBT?p=preview
This demonstrates an event being broadcast from one controller to another AFTER the 2nd controller (LegCtrl in our example here) is loaded via ng-include and passing it data.
I used $includeContentLoaded event from ng-include to delay broadcasting the event until angular reports that add_leg.html is loaded. Also I think there were some issues with how you were using $broadcast()... it's first parameter should be the same as the one used in $on() in LegCtrl
Another idea to consider is simply using your Navigation service itself to share state between the controllers if that's appropriate in your scenario.
Can I implement something like:
$scope.showDashboard = function () {
$scope.dashboardPath = "/Widgets/Weather/View.htm";
$scope.widgetController = 30;
require(['/Widgets/Weather/Controller.js'], function (w) {
whatShouldIputHere = w;
});
};
<div ng-include src="dashboardPath" ng-controller="whatShouldIputHere?"></div>
Is it possible to assign a controller to ng-include dynamically?
There could be many widgets on the dashboard
There is a project in development that implements dashboard functionality with AngularJS.
Features:
Adding/removing widgets
Widgets drag and drop
Any directive can be a widget
Running Demo http://nickholub.github.io/angular-dashboard-app
Demo source code https://github.com/nickholub/angular-dashboard-app
Dashboard directive itself https://github.com/nickholub/angular-ui-dashboard
We created an angularjs based dashboard in the open source hawtio project. You can noodle the code here if you like:
https://github.com/hawtio/hawtio/tree/master/hawtio-web/src/main/webapp/app/dashboard
For each widget on the dashboard we compile the partial directly with a child scope
https://github.com/hawtio/hawtio/blob/master/hawtio-web/src/main/webapp/app/dashboard/js/dashboard.ts#L142
Though we had to patch angularjs to allow us to use custom injection on child scopes. e.g. so that we can use a different implementation of $location for each child widget (so it thinks its on its own real URL etc). Hopefully when custom injectors are supported we can move to that.
Instead of using dynamic controllers why not use a single controller(the one which has the showDashboard method). Adding dynamic controller with ng-include will result in nested controllers which is illegal i guess. And instead of using ng-include as an attribute use it as an element.
<ng-include src="dashboardPath"></ng-include>