Using Angularjs 1.5, I have been having trouble making a D3.js graph that's defined in one component display on a page that relies on a different one. I'm new to Angular, and while I think the answer is buried somewhere in the official documentation's example of a component tree, I wasn't able to completely follow the logic.
My goal is to make the graph defined in graphController, and make it appear as accessed by requests.template.html.
Trying to inject it into requests.component like I would a directive gave me an unknown provider error
I inherited the setup from someone else (and would prefer to leave the protect structure the way it is), and it more or less looks like this:
requests.module.js
angular.module("requests", []);
requests.component.js
angular.module('requests').component('requests', {
templateURL: 'Bindings/Templates/requests.html',
controller: function requestsController($scope) {
[code]
}
}
requests.template.html
//where I'd like to be able to access the controller from the graphs component
graphs.module.js
angular.module('graphs', []);
graphs.component.js
angular.module('graphs').component('graphs', {
templateURL: '/Bindings/Templates/graphs.template.html',
controller: function graph() {(code for d3 graph)};
}
graphs.template.js
{{$ctrl.graph()}}
//this page is just a placeholder to see the graph until I can view it on requests' page
Any help you can give or ways to think about how I should thread this controller call through would be great. Thanks!
If you are wanting the graph to display in the requests component then you can just insert the component element tag within the requests template. For example, in the Bindings/Templates/requests.html insert:
<div class="stuff-to-wrap-graph">
<graphs></graphs>
</div>
Once you have created a component, think of it like a unique html element that you can insert anywhere, including within other components. You would normally only want direct access to the controller of another component if it was a tightly coupled child of a parent, eg. an individual tab within a tabs component that needs to talk to the parent tab container.
Related
I am having trouble with an instance of a controller.
I have a simple navigation tree using this module:http://nickperkinslondon.github.io/angular-bootstrap-nav-tree/test/bs3_ng120_test_page.html
I added a double click function on any leaf, and a directive that compiles, and opens a window using this module: http://codepen.io/m-e-conroy/pen/cFKkG
The window contains a few tabs, and one of these tabs must contain another navigation tree, just like the first one but with different data.
I am also using http://www.simplygoodcode.com/2014/04/angularjs-getting-around-ngapp-limitations-with-ngmodule/ so I can have multiple app on the same page.
Problem is, the second navigation tree that must be loaded doesn't work. I am declaring it, in the html sent this way (moduleAbn is a variable contained in the first .js loaded at start, referecing the module App1):
<script> moduleAbn.controller('AbnTestController2',['\$scope','\$http','\$timeout','\$compile',function(\$scope,\$http,\$timeout,\$compile){
...}]);</script>
<div ng-module='App1'> <div ng-controller='Controller2'>
Doing this, the console shows the famous Controller2 is not a function, got undefined.
The weird thing is, if I declare it with the name of the first navigation tree controller
<div ng-module='App1'> <div ng-controller='Controller1'>
Navigation tree appears, with the same data as the first, but seems to work independently from the other.
I really don't know how do get this working with different data from the first tree...
If I am not being clear, english is not my mother tongue so I can get more specific if you have any questions ;)
Cheers!
You should rename AbnTestController2 to Controller2 when defining it or change it's name in ng-controller= from Controller2 to AbnTestController2.
Second thing you need to check is that App1 has module AbnTest as dependency:
var App1 = angular.module('App1', ['AbnTest'])
First of all, I have read so much info you all share about communication between controllers, that I'm now so confused about the BEST way to do it. I understand I should use service, but don't really know what it means from the examples you all wrote.
Here is my scenario:
Left side of my page I have a side bar with a simple folder/files tree. I have used angular Tree Component and it works great for me: http://jimliu.github.io/angular-ui-tree/
On the right side of the page, I have a view that is being changed by user navigation.
Each time user select any node on the tree, I want to be able to tell any right side page controller which node has been selected. Each selection on tree should notify any one who want to listen to that change.
I don't like the idea of using $rootScope and fire event from the service, Is that the only way?
To illustrate how it can be done, I created some example code using a service to share data between Controllers and Directives:
app.factory('SharedService', function() {
return {
sharedObject: {
value: ''
}
};
});
http://plnkr.co/edit/Q1VdKJP2tpvqqJL1LF6m?p=preview
Hope that helps
I'm working on a single page app that behaves similar to a wizard. The user lands on page 1 and needs to save and continue to get to page 2. What's the best way to share those buttons between all the views? My form names are different so I currently have to duplicate these buttons so I can use logic like:
<div class="mySaveButton" ng-disabled="page1Form.$invalid"></div>
but then on page 2:
<div class="mySaveButton" ng-disabled="page2Form.$invalid"></div>
To further complicate matters, saving on page1 posts the data to a different address than page2. I have a navigation controller which is the parent and that needs to be handled as well.
So to summarize I need my buttons (Back, Save and Save and Continue) to do all of the following without having to duplicate the buttons across all views:
Check if the current form is valid
If it's valid, the data for that form needs to post to the correct endpoint for that form
Navigation needs to be notified so that it can update and/or take action
Essentially you need to re-use a template (/controller), and somehow pass options into it. You could probably do something with ngInclude and nesting controllers, but a way that is more friendly to more complex structure later is to create a custom directive. It depends a bit on your exact use-case, and exactly what you need to pass into it, but a simple version would be something like:
app.directive('formButtons', function() {
return {
restrict: 'E',
scope: {
'localBack':'&back',
'localSave':'&save',
'localSaveAndContinue':'&saveAndContinue'
},
templateUrl: 'buttons.html'
};
});
With a template of
<div>
<button ng-click="localBack()">Back</button>
<button ng-click="localSave()">Save</button>
<button ng-click="localSaveAndContinue()">Save & Continue</button>
</div>
The scope options with & each define a function on the directive's scope, that evaluates contents of the attribute on the <form-buttons> element of that name in the parent scope. You can then use the directive as follows
<form-buttons back="back(2)" save="save(2)" save-and-continue="saveAndContinue(2)"></form-buttons>
which will get replaced by the template of the directive, and the function save, back and saveAndContinue, which are defined on the parent scope, will get called when clicking on the buttons in the template, passing the appropriate step number which can be used to customise behaviour.
You can see a working example at http://plnkr.co/edit/fqXowQNYwjQWIbl6A5Vy?p=preview
I'm building a dashboard system in AngularJS and I'm running into an issue with setting the url via $location.path
In our dashboard, we have a bunch of widgets. Each shows a larger maximized view when you click on it. We are trying to setup deep linking to allow users to link to a dashboard with a widget maximized.
Currently, we have 2 routes that look like /dashboard/:dashboardId and /dashboard/:dashboardId/:maximizedWidgetId
When a user maximizes a widget, we update the url using $location.path, but this is causing the view to re-render. Since we have all of the data, we don't want to reload the whole view, we just want to update the URL. Is there a way to set the url without causing the view to re-render?
HTML5Mode is set to true.
In fact, a view will be rendered everytime you change a url. Thats how $routeProvider works in Angular but you can pass maximizeWidgetId as a querystring which does not re-render a view.
App.config(function($routeProvider) {
$routeProvider.when('/dashboard/:dashboardId', {reloadOnSearch: false});
});
When you click a widget to maximize:
Maximum This Widget
or
$location.search('maximizeWidgetId', 1);
The URL in addressbar would change to http://app.com/dashboard/1?maximizeWidgetId=1
You can even watch when search changes in the URL (from one widget to another)
$scope.$on('$routeUpdate', function(scope, next, current) {
// Minimize the current widget and maximize the new one
});
You can set the reloadOnSearch property of $routeProvider to false.
Possible duplicate question : Can you change a path without reloading the controller in AngularJS?
Regards
For those who need change full path() without controllers reload
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
I realize this is an old question, but since it took me a good day and a half to find the answer, so here goes.
You do not need to convert your path into query strings if you use angular-ui-router.
Currently, due to what may be considered as a bug, setting reloadOnSearch: false on a state will result in being able to change the route without reloading the view. The GitHub user lmessinger was even kind enough to provide a demo of it. You can find the link from his comment linked above.
Basically all you need to do is:
Use ui-router instead of ngRoute
In your states, declare the ones you wish with reloadOnSearch: false
In my app, I have an category listing view, from which you can get to another category using a state like this:
$stateProvider.state('articles.list', {
url: '{categorySlug}',
templateUrl: 'partials/article-list.html',
controller: 'ArticleListCtrl',
reloadOnSearch: false
});
That's it. Hope this helps!
We're using Angular UI Router instead of built-in routes for a similar scenario. It doesn't seem to re-instantiate the controller and re-render the entire view.
How I've implemented it:
(my solution mostly for cases when you need to change whole route, not sub-parts)
I have page with menu (menuPage) and data should not be cleaned on navigation (there is a lot of inputs on each page and user will be very very unhappy if data will disappear accidentally).
turn off $routeProvider
in mainPage controller add two divs with custom directive attribute - each directive contains only 'templateUrl' and 'scope: true'
<div ng-show="tab=='tab_name'" data-tab_name-page></div>
mainPage controller contains lines to simulate routing:
if (!$scope.tab && $location.path()) {
$scope.tab = $location.path().substr(1);
}
$scope.setTab = function(tab) {
$scope.tab = tab;
$location.path('/'+tab);
};
That's all. Little bit ugly to have separate directive for each page, but usage of dynamic templateUrl (as function) in directive provokes re-rendering of page (and loosing data of inputs).
If I understood your question right, you want to,
Maximize the widget when the user is on /dashboard/:dashboardId and he maximizes the widget.
You want the user to have the ability to come back to /dashboard/:dashboardId/:maximizedWidgetId and still see the widget maximized.
You can configure only the first route in the routerConfig and use RouteParams to identify if the maximized widget is passed in the params in the controller of this configured route and maximize the one passed as the param. If the user is maximizing it the first time, share the url to this maximized view with the maximizedWidgetId on the UI.
As long as you use $location(which is just a wrapper over native location object) to update the path it will refresh the view.
I have an idea to use
window.history.replaceState('Object', 'Title', '/new-url');
If you do this and a digest cycle happens it will completely mangle things up. However if you set it back to the correct url that angular expects it's ok. So in theory you could store the correct url that angular expects and reset it just before you know a digest fires.
I've not tested this though.
Below code will let you change url without redirection such as: http://localhost/#/691?foo?bar?blabla
for(var i=0;i<=1000;i++) $routeProvider.when('/'+i, {templateUrl: "tabPages/"+i+".html",reloadOnSearch: false});
But when you change to http://localhost/#/692, you will be redirected.
From my understanding, the differences is the callback functions to events on an AppRouter should exist in the Controller, instead of the same Router object. Also there is a one-to-one relationship between such AppRouter & Controllers, all my code from Router now moves to Controller, I don't see too much point of that? So why use them? I must be missing something?
The way I see it is to separate concerns:
the controller actually does the work (assembling the data, instanciating the view, displaying them in regions, etc.), and can update the URL to reflect the application's state (e.g. displayed content)
the router simply triggers the controller action based on the URL that has been entered in the address bar
So basically, if you're on your app's starting page, it should work fine without needing any routers: your actions (e.g. clicking on a menu entry) simply fire the various controller actions.
Then, you add on a router saying "if this URL is called, execute this controller action". And within your controller you update the displayed URL with navigate("my_url_goes_here"). Notice you do NOT pass trigger: true.
For more info, check out Derick's blog post http://lostechies.com/derickbailey/2011/08/28/dont-execute-a-backbone-js-route-handler-from-your-code/ (paragraph "The “AHA!” Moment Regarding Router.Navigate’s Second Argument")
I've also covered the topic in more length in the free preview of my book on Marionette. See pages 32-46 here: http://samples.leanpub.com/marionette-gentle-introduction-sample.pdf
I made some override for the router. And currently use it in this way (like Chaplin):
https://gist.github.com/vermilion1/5525972
appRoutes : {
// route : controller#method
'search' : 'search#search'
'*any' : 'common#notFound'
},
initialize : function () {
this.common = new Common();
this.search = new Search();
}