I previously thought that the controller script would only be executed once, when my single page application is loaded.
Now I see that every time I change a view my controller script executes again. I can tell because i had a couple of statements in the script that are not nested in functions. I want them to happen only when the app is first loaded, but they are running when I change views.
The views are configured in my app.js with module.config:
myModule.config(function ($routeProvider) {
$routeProvider
.when('/search', {
templateUrl: 'views/search.html',
controller: 'searchCtrl'
})
.when...
So, is it normal for the controller script to run when the view changes? Or should I be searching for something I have configured wrong?
Yes this is normal. It was(/is) the same for me, however I use angular-ui-router... The solution I chose in order to make sure that the data in the controller would persist was to use a factory, you could use a service or provider just as well.
"Specialized objects conform to a specific Angular framework API. These objects are one
of controllers, directives, filters or animations.
The injector needs to know how to create these objects. You tell it by registering a
"recipe" for creating your object with the injector. There are five recipe types.
The most verbose, but also the most comprehensive one is a Provider recipe. The
remaining four recipe types — Value, Factory, Service and Constant — are just
syntactic sugar on top of a provider recipe."
https://docs.angularjs.org/guide/providers
Related
I have a controllers folders in www/js/controllers where I keep all the controllers needed by my app. But when I start the app all the controllers are loaded at once. Now how can I load only those controllers whose view is being used and keep other controllers at peace.
To dynamically load controllers, you need to use RequireJS.
http://requirejs.org/
Using https://oclazyload.readme.io/docs/with-your-router
Or,
Try this:
If you are using ngRoute:
$routeProvider
.when('/url',
{
templateUrl: '/views/abc.html',
resolve: resolveController('/controllers/abc.js')
});
Using UI-Router:
.state('abc',{
url : '/abc',
templateUrl : 'views/abc.html',
resolve: resolveController('/controllers/abc.js')
})
I don't know what you are trying to ask exactly.
See, JavaScript load All files(even won't take much time) while execute the app. Note it, files load only(allocate the memory) not execute the function except on-fly function. Ex (Angularjs, Jquery, Underscore - the file load first time only but function execute when we use).
Angularjs is JS framework right?. So same like above, here we are telling to execute controller in routing or ng-controler directive as per view, Note the file was loaded before only. In single view we can execute many controllers(ng-controller) as per need.
Create Controllers & Services in IIFE based. It's good.
I was reading all the answers about communication between controllers and directive, but is seems to me occurred using shared service and inject it to each one of them. When I'm developing a very large scale application, I don't know what my page is going to have in it. I may have 2 controllers need to communicate between them, and I also may have 5 directive and 2 controllers in the same page. I don't know from scratch what is going to be inside my view/page, so I need a better way how to communicate between them. I'm looking for that better way to do it, the right AngularJS way.
http://i60.tinypic.com/2z87q05.png
Above is my view example I'm working on: on the left side I have a directive tree, on the right side I have chart controller and directive grid. I may have more than this, but this is a good example of what I may have. Keep in mind, the view can have X componenets, you don't know from the beginning what will be in it.
Now, lets say each time I select node in tree on the left, I want to be able to tell the other controllers that nodeSelectedChange event happend. I don't want to inject each one of them a service that holds that info, so I thought about something like having a Page Manager Controller, which is the father of the all view. All my controllers/directives inside my page should talk with each other only by the PageManagerController, he is the only thing in page that knows what he has inside of it.
Important here to keep in mind: The tree don't know the page has chart or grid, they don't need to know each other in order to communicate. The page manager knows everything, now I want it to make the magic - Should it has a service? Should each other component has service and service can talk with PageManager service?
Help me to think. Hope I can connect all the dots to create a BETTER way for communication.
I think the sharedService way is good for small app, when you know from start what is going on in your app, but most of the time - You just don't know who and when is going to use your directive, it should be able to talk with everyone.
What I don't like about events:
The events being fired inside the controller, while it should be inside a service.
The listener controller should know the name of the event he is going to listen too. If I change the event name being $emit or $broadcast, I need to go all over the listeners $on("eventName") in all app and change to that unique name.
Directive is like a black box, I don't want to check inside of it each time and find the names of the events he is being broadcasting in order to communicate with it.
I need a way to exposed the events NAMES out of the directive, probably with a service connected to that controller.
There are a couple of good practices:
Use the $scope to communicate between directives and controllers for runtime dependencies (e.g. models you fetch from the server, instantiated classes, etc.). The way to go is to have an isolated scope with attributes mapped to the dependencies:
directive('tree', function(){
return {
scope: {
somevalue : "="
}
}
});
Use it like this:
<tree somevalue="objectFromController">
Use the injected services to communicate with static dependencies (Presentation Models, global sharable state, etc.)
directive('tree', function(treeState){
return {
scope: {
somevalue : "="
},
link: function(scope){
// some logic updating the treeState
treeState.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeState){
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
If you want to have maximum composability, stick to the first pattern:
directive('tree', function(){
return {
scope: {
model : "="
},
link: function(scope){
// some logic updating the treeState, stored as scope.model
scope.model.openNodes = ['x', 'y'];
}
}
});
controller('ctrl', function($scope, treeFactory){
$scope.treeModel = treeFactory.create();
// react to treeState changes
// you can use $scope.$watch for it
// or any other way you like
// see: https://github.com/mr-mig/angular-react-to
});
And compose this stuff using template and binding as a communication bus:
<tree model="treeModel">
Sticking to this pattern you get:
No events
Well-defined directive "interface" (attributes in the isolated scope)
Easy composability
Reactive behavior based on scope change propagation
It really depends on the type of information you want to share from the tree directive to the other sections of the page.
You have a few options in front of you:
Use Scope Events
As someone mentioned above, you could fire an event and listen for events in various controllers and/or services. That said, it can get really ugly, really fast. Tracing events and figuring out what event listeners are active at a given point can give the best engineers a migraine!
Use a Service
Another option would be to use a Service, let's say a PageManagerService. In that case,
Each click of a tree item would set some information on the PageManagerService, saying which page, what objects, and what items it needs to display
Each component that needs to change could
Register a listener to be triggered when the PageManagerService changes
Add a watch on the service and run its code when the PageManagerService changes
The service itself is just going to be shared state, and it would be upto the directives and components on how it wants to consume and respond to changes in the state.
Use UI Router
But the more I think about this, the more it seems like a good use case from something like UI Router. UI Router allows you to define states, and have different parts of the page respond in different ways to state changes. Each section could respond to a state change in its own way, by loading
A different controller
A different template, possibly with different components and widgets
So what you would end up having is a structure with
The Tree directive on the left
A ui-view named, let's say, top
A ui-view named, let's say, bottom
Each item in your tree directive can then be a ui-sref, which is just a fancy way of saying instead of redirecting to a URL, redirect to a state.
You could then define your configurations in your application in a single place, like so:
$stateProvider.state('dashboard', {
views: {
"top": { templateUrl: 'my/dashboard.html', controller: 'DashboardCtrl'}
"bottom": { templateUrl: 'my/dashboard-grid.html', controller: 'DashboardGridCtrl'}
}
})
Similarly, you could have a state definition for each item in your tree directive, and each one just be a link to a different state.
Of course, the state definitions are done in your config section in an AngularJS application, which you would know is before the application starts. What if you needed dynamic states as well?
Well, a few answers / thoughts to lead you down the way for that as well:
The controllers and services would have to be predefined, you would not be dynamically creating HTML content and/or JS controllers
It is possible to expose the $stateProvider as a global variable in the config section, and dynamically call stateProvider.state inside a controller / service, wherever you have new state definitions.
More often than not though, the controllers and HTML remain constant, and we just need to trigger the different states with various parameters. That can easily be done by calling transitionTo function to transition to a defined state with various state parameters.
you can use $on, $emit and $broadcast to communicate among different controllers/scopes .
Please follow one of my earlier post . I have a setup a plunk . You can try out the example.
Angular Js newbie - link in a controller view that triggers another controller action
$on : setup a event handler
$emit : communicate to parent controllers
$broadcast : communicate to child controllers
To know more visit - https://docs.angularjs.org/api/ng/type/$rootScope.Scope
I'm using an angularJS and requireJS seed which you can download here: LINK
In that seed only the controllers that are called download the relevant controller which is then triggered. I've been trying to call a factory from my controller (with no luck) plus I would like the services/factories only to download the relevant factory if it has been called.
I've attempted to require a function within the factory method (much like the controller) but it is not working.
This is where I left off: Plunkr link
user971824.
I've put together something I call couchPotato that lazy-registers just about anything in angular using requirejs and the resolution features of $routeProvider (or any other router that does lazy promise-based resolution.
I've created a plunker based on yours demonstrating how you could do it with couchPotato. If you take a look at it, I think you'll see that it's a bit simpler because you don't actually create modules for all of the things you register lazily.
couchPotato grew out of some other example apps I found on the web. What I wanted was a tight way to do the lazy registration and a provider/service combo seemed ideal. I also wanted to maintain the ability for one component to depend on another, like in your example, you want the controller to depend on the factory... couchPotato lets you specify those dependencies in requirejs syntax.
So your controller, in my rendition, looks like this:
define(['app', 'myFactory'], function(app) {
app.couchPotato.registerController([
'mycontroller',
[
'myFactory',
function(myFactory) {
var message = myFactory.getCustomers();
alert(message);
}
]
]);
});
In this example, I made the controller, the factory and the value all lazy, but you could pick and choose and have some registered the "old fashioned way" at configuration time and others registered with couchPotato when they're needed for a given route.
http://plnkr.co/edit/Z3v1mszQiiq024po8Ocp?p=preview
A couple of things to note:
1) I put in a default route in order to trigger the lazy loading of your controller, your service (factory) and the version value.
2) I modified your service to append the version just to show how one component can depend on another (the service depends on the version value, the controller depends on the service).
So, within the require configuration, you don't actually specify any of this... it's all done lazily within your route.
$routeProvider.when('/',
$couchPotatoProvider.resolveDependenciesProperty({
templateUrl:'home.html',
controller: 'mycontroller',
dependencies: [
'mycontroller'
]
})
);
Since mycontroller depends on myFactory and myFactory depends on version, by the time your route is displayed they are all available and hooked up. I put some dummy text in the home.html partial just for kicks, but the controller is assigned by the $routeProvider so you don't actually need to specify it in the template.
couchPotato lives at https://github.com/afterglowtech/angular-couchPotato if you'd like to see a couple of other samples. I shim'ed it in dependent on angular because I designed it to be usable in cases where an entire application doesn't necessarily use requirejs... thus if you are loading angular with requirejs, you need to make couchPotato dependent on angular using the shim/deps technique.
LMK if you have any questions/comments... hope this helps!
I wonder what's the best way to configure a route which only purpose is to make use of a service call and then redirect.
I'm currently using this hack:
$routeProvider.when('/talks', {
template: '<div></div>',
controller: ['dateService', '$location', function(dateService, $location){
var nextTalk = dateService.getNextTalkDate();
$location.path('talks/' + nextTalk.format('MM') + '/' + nextTalk.format('YYYY'));
}]
});
Currently I set the controller configuration to an inline controller implementation which has dependencies on the services and then does it's thing and redirects.
However, it feels a bit weird, since I have to set template to some dummy value because otherwise the controller would not be invoked at all.
It feels as if I'm stressing the controller property for something it wasn't intended for.
I guess there should be a way to run some view unrelated code that has access to services on a route. I could right my own $routeProvider I guess but that seems to be a bit heavy for something I would consider should be built in.
Looks like '/talks' is a kinda abstract since its just used to redirect to other routes.. So how about setting up a route like this:
''/talks/:month/:year'
Where :month and :year is optional. If no month or year is given, your service returns a default talk. Which is probably the next talk. If params are given you just fetch the requested data.
So there's no redirect required. Now you specifiy the controller and the needed view and expose your data on the scope. Optionally would it be better to wrap your service call in a promise and resolve it at routeProviders resolve property.
This makes sure that the view only change if everything's resolved fine.
Hope that helps!
There's no way to inject services into anywhere into a route. Whether it be redirectTo or template, any code in there is handled on the module level. This means that the module is loaded first and then when the application has boostrapped itself then the services and injection-level code is executed. So the controller is your best bet (since that does support injection).
Controller is used in a lot of areas in AngularJS and it should work fine for what you're trying to do. You can either handle the redirection like you do in there or you can setup three different routes that point to the same controller (where you build the page).
var handler = { controller : 'Ctrl' };
$routeProvider.when('/talks', handler).
when('/talks/:month, handler).
when('/talks/:month/:year', handler);
And if you do end up using redirection, then just use $location.path(url).replace(). This will make the history stack jump back one level and therefore that the redirection triggering URL won't be in your history.
Here my app.js code :
Ext.Loader.setConfig({
enabled: true
});
Ext.application({
name: 'KP',
appFolder: 'scripts/app',
controllers: [
'login.Login'
],
views: [
'login.Login'
],
launch: function () {
var view = Ext.widget('login')
}
});
If I want to use some others views, controllers, models and stores in my application, should I define them in app.js? (like this : controllers[....all of my controllers.....])
Are there other way to get to work init function in my controllers? Thanks!
There are many ways...
Following some basics first:
All controllers that are listed within the controllers array of the application controller get instantiated at startup (application get initialized with the onReady event. Also the init() and onLaunch() methods of the listed controllers get called. See the linked API for details when this occurs). Now each instantiated controller initialize its stores,views and models (creates getter for all and over that creates instances of each store while overwriting the storeId and append them to the Ext.StoreMgr). Note that each controller will contains his own models, stores and views.
The simplest way to receive a controller that is not listed in the application controller is by using a reference of the application controller and calling getController(name)
It is Important to know that while using that way the receive a controller instance the getter method will not call the init() or onLaunch() method for the invoked controller, it will just try to get the controller or create it. You should also note the API for those methods in these controllers are no longer correct. You need to set an init bool to these controllers and check it on the reference that the getController(name) method hands back to you. If it is undefined/false you call the init() / onLaunch() by yourself and set it after that. I use these technique by myself to reduce intial load for bigger application. I guess you don't really need this for just a handful of controllers.
Update
Due to some changes in the 4.1.x release it is no longer required to init the controller
manually. This is now done for us by the getController method
You can define as many controllers as you want. To implement hierarchy you can define views and stores related to certain controller within it.
For example:
controller/
artists.js (inside: artistsView1.js, artistsView2.js, artistsStore.js)
paintings.js (inside: paintingsView1.js, paintingsStore.js)
All of your views and stores used in controllers would be loaded and initializadduring Applocation load.
The controllers you define in your application are loaded before the application launch() is called. As each controller is loaded its models, views and stores (MVS) are also loaded.
Although you can define the MVSs in your application, I'd recommend that each controller defines its related MVSs, as it is possible for the programmer to load controllers (and their related MVSs) upon request rather than by default. It is also a good practice from reusability point of view (so if you ever to reuse the controller, you know what MVSs come with it).
You may want to include some views in the application if, say, they are used without controllers, like some sort of a custom window you display if there was some error in the system.