I'm just learning Angular and have a very basic app set up. When rendering some data via the templateUrl property of a route, what would be the best way to include a sub-controller in the returned template? For example, including a "createOrEditItem" template at the bottom of a "viewItem" template so that the "createOrEditItem" can be reused on its own later?
I've tried putting a div in the template with its ng-controller attribute set to a controller name that I've defined at the app level, but it's not being activated. Should this be done with a directive instead to make it instantiate when the master controller has its contents set, or am I missing something more fundamental?
yes, as mentioned in the later part of the question, you should be using a directive. Or, if using AngularJS >= v1.5, component should be the choice because they are pluggable and works well with nesting too.
Note that for the route also, you can directly use a component like this:
var myMod = angular.module('myMod', ['ngRoute']);
myMod.component('home', {
template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
// ^^^^ other components can be used here
controller: function() {
this.user = {name: 'world'};
}
});
myMod.config(function($routeProvider) {
$routeProvider.when('/', {
template: '<home></home>'
});
});
Now, as the comment suggests, you can freely use other components in the template of home component.
Hope this helps a bit!
A directive can be used.
Another option is to use a seperate view/route. So when you add a ui-view tag, you could define your view and route.
This is explained here:
https://scotch.io/tutorials/angular-routing-using-ui-router
Related
I don't want to hardcode views and controllers in the normal way by using the $routeProvider configuration.
Basically I want to subsribe to the routechanged event, call some logic on the server providing the current route, and get the appropriate view / controller returned as json ( as a promise ), and then have it compiled / displayed inside the main section of the app.
It seems like a trivial problem, but I haven't found a solution yet.
I've tried replacing the ng-view with ng-include, but i'm not able to dynamically change the ng-controller on it.
I've tried creating a custom directive which optionally wraps the ng-include in a div with ng-controller if it is provided, but since this executes inside template function, i'm not able to resolve the controller name, because the template function does not have access to scope
If any one can show me the path, it would be greatly appreciated.
I've ended up doing this in my controller, which seems to work:
function loadPage(page) {
var container = $("#main");
var element = $("<div>");
var scope = $rootScope.$new();
server.loadViewTemplate(page.view).then(function(template) {
element.html(template);
container.html(element);
if (page.controller) {
var templateController = $controller(page.controller, { $scope: scope });
element.children().data('$ngControllerController', templateController);
}
$compile(element)(scope);
});
}
In a component I would like to associate templates to CRUD actions, but use a single controller that handles data for all all of them. Does this make sense with angular components, or should I use several components ?
How would the ui-router state configuration look like ?
EDIT:
Components have templates, and ui-router states too. I am confused about the articulation of those 2 concepts.
EDIT 2:
trying to clarify my understanding so far :
component are controller + template + bindings. Controller can be omitted.
states are url + template + controller or url + component. Controller can be omitted.
So it seems that components are taking away some of the responsabilities that use to belong to ui-router.
My goal here:
- url1 --> controller foo + template x;
- url2 --> controller foo + template y;
- url3 --> controller foo + template z;
should I do :
components:
component x --> controller foo + template x;
component y --> controller foo + template y;
component z --> controller foo + template z;
and then routing:
url 1 --> component x
url 2 --> component y
url 3 --> component z
?
EDIT 3:
quote from https://docs.angularjs.org/guide/component :
"In a component-based application, every view is a component"
quote from https://ui-router.github.io/guide/ng1/route-to-component :
"The component model enforces separation of concerns and encapsulation by using an isolate scope. Data from parent scopes can no longer be directly accessed. Instead, it needs to be explicitly wired in"
Yes, it is possible. Your ui-router config would look something like this: (Multiple states having same controllers.)
.state('add', {
url: '/add',
templateUrl: 'templates/add.html',
controller: 'contactCtrl'
})
.state('edit', {
url: '/edit',
templateUrl: 'templates/edit.html',
controller: 'contactCtrl'
})
Here's a working example of having multiple states and templates using same controller.
Edit: You don't have to use components. Why create three different extra components while you can achieve the same thing without them? I would still recommend the approach I mentioned above. Getting the same outcome with lesser code should always be chosen. :)
Quoting Eric Elliot on twitter,
Code is temporary. It exists while it is useful.
If code is replaced by better code, good!
If code can be deleted, celebrate!
Your state provider will look like this
JS code
$stateProvider
.state('report', {
views: {
'filters': {
templateUrl: 'report-filters.html',
controller: 'ctrl'
},
'tabledata': {
templateUrl: 'report-table.html',
controller: 'ctrl'
},
'graph': {
templateUrl: 'report-graph.html',
controller: 'ctrl'
}
}
})
in single state you can load multiple views
HTML
<body>
<div ui-view="filters"></div>
<div ui-view="tabledata"></div>
<div ui-view="graph"></div>
</body>
refer multiple views
angular.module('', [])
// Route handler touches this
.component('route1', {
template: `<shared-behaviour>
<route-view-1></route-view-1>
</shared-behaviour>`
})
// This is a purely visual component that links actions with the shared behaviour component
.component('routeView1', {
require: {
shared: '^sharedBehaviour'
},
template: '<button ng-click="$ctrl.onClick()></button>',
controller: class RouteView2Ctrl {
onClick() {
this.shared.onSharedClick()
}
}
})
// Contains the shared behaviour that is shared across the multiple routes
.component('sharedBehaviour', {
// Tell Angular to render children passed to this component
transclude: true,
template: '<div ng-transclude></div>',
controller: class SharedBehaviourCtrl {
onSharedClick() {
/// do something
}
}
})
// Newest version of UI-Router
.state('route1', {
component: 'route1'
})
Further to our comments, you could use something like the above. It's pretty verbose, but that's Angular 1.5 for you. Haven't tested that it works, but it's a basic idea of how it might work.
The main take away being that you have the route1 component which only handles linking route stuff (like the url, resolves, etc) and passes that information to its children (of which there aren't any).
routeView1 just renders how the route would work, and uses require to talk to some parent API that is shared (you could also use one-way bindings for this if you wanted, but with lots of methods this leads to a messy template).
sharedBehaviour only contains the shared behaviour you want renders any child passed to it.
This is admittedly pretty messy and I would prefer that you instead used one-way bindings for route-view-1 and handled the linking in shared-behaviour, but this might be quite a bit of repetition. You could use some transclusion magic to solve that, but it's.. not really worth it.
I would really recommend against sharing controllers like other answerers have suggested. In Angular 2 and above, a controller is the component. You share behaviour there by doing something similar to what I have done (or by using one-way bindings with callbacks) and using composition.
BTW as mentioned in my answer, newer versions of UI-Router for Angular 1.5 allow you to specify a component to be rendered rather than a template. See here for more info.
I'm using Angular 1.5+ with Typescript, preparing my code to be compatible with Angular 2. I've got a situation where many of my components need to use an application-wide repository location for the views that are mapped to their templateUrl properties, but sometimes a view needs a specific, local implementation.
So, normally the views are hosted on a fast-served CDN, they're re-used between multiple sites that all belong to the same general code base, api, etc. This is done to prevent duplicating them and to centralize what doesn't need to be repeated.
Rarely, I'll need to override this behavior and use a more specific, fine-tuned view. So my approach to this was to add a binding to the components called viewLocation, with the intent to use it like this;
<component-name></component-name> is the default. In this situation, the default CDN path is used.
<component-name view-location="local"></component-name> is a unique situation. If this happens, the templateUrl should be able to respond to that and switch to a path relative to the specific application and not from the CDN.
I thought it would be pretty simple until I realized that I wouldn't have access to the binding properties within the actual constructor or templateUrl function, as is demonstrated here;
export class SidebarComponent extends Component {
constructor() {
super();
this.bindings = { viewLocation: '=' };
// other properties
this.templateUrl = ($someService: IServiceInterface): string => {
// help! I don't have access to the viewLocation property!
}
}
}
So is there anything else I can do to get access to that property?
This is not done in TS, but the AngularJS 1.5 component generally provides the $element and $attrs to the $injector when you are using an injectable template.
This is an example in AngularJS using Javascript where the template URL is picked based on an attribute set on the component:
angular.module('example', []);
angular.module('example').component('myComponent', {
templateUrl: function($element, $attrs, $log) {
$log.info('determining template to be used');
if($attrs.useTemplate) {
return $attrs.useTemplate;
}
return 'default.html';
}
});
Template snippet:
<my-component use-template="hui.html"></my-component>
<my-component use-template="bu.html"></my-component>
<p></p>
<my-component></my-component>
Working example:
template injection in an angular 1.5 component
I don't want to use templateUrl, due to the way the app is currently structured.
I want to be able to dynamically load a controller into my ng-controller template.
I would think there would be a way to pass variable to ng-controller as you can see
I'm passing controller to ng-controller ignorantly hoping the name controller is assigned
to index_projects.
Routing
config = (http,route) ->
http.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
http.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"
route_to = (path,controller)->
route.when path, {controller: controller}
route_to '/projects', 'index_projects'
Template
.projects_wrap{ ng:{controller:'controller'} }
%h2
%span All Projects
.light_button{ ng:{click: 'pop.new()'} }
%span.light_plus
Add Project
Look at this:
http://www.bennadel.com/blog/2441-Nested-Views-Routing-And-Deep-Linking-With-AngularJS.htm
I am successfully freed of using ngView with this article.
I have a route defined as
$routeProvider.when('/:culture/:gameKey/:gameId/closed', { templateUrl: '/templates/tradingclosed', controller: TradingClosedCtrl });
I would like angular to include the "culture" parameter when requesting the template somehow, so I can serve a translated template.
Is this possible?
If I'm reading this correctly you'd like to somehow use the culture parameter from the url route to determine which location to retrieve your template.
There may be a better way but this post describes retrieving the $routeParams inside a centralized controller with ng-include to dynamically load a view.
Something similar to this:
angular.module('myApp', []).
config(function ($routeProvider) {
$routeProvider.when('/:culture/:gameKey/:gameId/closed', {
templateUrl: '/templates/nav/urlRouter.html',
controller: 'RouteController'
});
});
function RouteController($scope, $routeParams) {
$scope.templateUrl = 'templates/tradingclosed/' + $routeParams.culture + '_template.html';
}
With this as your urlRouter.html:
<div ng-include src="templateUrl"></div>
You can define the controller you want to load in your views using ng-controller and access the $routeParams for the additional route parameters:
<div ng-controller="TradingClosedCtrl">
</div>
I've posted similar question with working Plnkr example of solution like #Gloopy suggested.
The reason why you can't implement that without ng-include is that routing is done in 'configuration' block, where you can't inject any values (you can read about these blocks in Modules documentation, section Module Loading & Dependencies
If you want not to introduce new scope, you can replace ng-include with my stripped version of ng-include directive, that do absolutely same that ng-include does, but do not create new scope: source of rawInclude directive
Hope that solution will satisfy your use case.