Angularjs pass ui-router controller $scope to data - angularjs

i want to pass a pageTitle data from ui-router based on a $stateParams and then update the title of the page using a directive
ui-router example:
.state('index.something.detail', {
url: '/{type}/{else}',
views: {
'': {
templateUrl: 'tempalte.html',
controller: function($scope, $stateParams) {
if ($stateParams.type == "param") {
$scope.title = "param";
}
}
}
},
data: {
pageTitle: 'Something {{title}}', //NOT WORKING
},
ncyBreadcrumb: {
label: 'Sometgin {{title}}', //IT WORKS
parent: 'index.something'
}
})
directive example:
function pageTitle($rootScope, $timeout) {
return {
link: function(scope, element) {
var listener = function(event, toState, toParams, fromState, fromParams) {
// Default title - load on Dashboard 1
var title = 'APP';
// Create your own title pattern
if (toState.data && toState.data.pageTitle) title = 'APP | ' + toState.data.pageTitle;
$timeout(function() {
element.text(title);
});
};
$rootScope.$on('$stateChangeStart', listener);
}
}
};
angular-breadcrumb pass the data ok
Something param
but in the pageTitle directive i get
Something {{title}}
how can i pass the $scope of the ui-router state?
Thanks!

We cannot pass this via data setting. The reason is, that the string param in data {} is just a string, not a view template. We would need to compile it and pass a scope.
But there is a better solution. Check this working example
Let's have index.html like this:
<H1><ui-view name="title" /></H1>
<ul>
<li><a ui-sref="parent">parent</a>
<li><a ui-sref="parent.child">parent.child</a>
</ul>
<div ui-view=""></div>
And our state can now inject stuff into two locations:
.state('state name', {
...
views : {
"" : {
// default view
}
"title" : {
template : "parent is setting title inside of index.html"
}
}
})
Check this here in action
Also, observe the UI-Router default sample application (doing the same trick) here:
http://angular-ui.github.io/ui-router/sample/#/
The most important part of the code is Contact state def, there is a small snippet:
// You can have unlimited children within a state. Here is a second child
// state within the 'contacts' parent state.
.state('contacts.detail', {
...
views: {
// So this one is targeting the unnamed view within the parent state's template.
'': {
...
},
// This one is targeting the ui-view="hint" within the unnamed root, aka index.html.
// This shows off how you could populate *any* view within *any* ancestor state.
'hint#': {
template: 'This is contacts.detail populating the "hint" ui-view'
},
// This one is targeting the ui-view="menuTip" within the parent state's template.
'menuTip': {

Related

Angular (1.5.8) Dynamic Components

I'm trying to build a sort of dynamic dashboard using Angular 1.5.8. I've made decent progress up until the final hurdle. Which is actually rendering the dynamic component.
I've tried 2 options, either adding a ui-view and programatically passing in the name of the widget, or, and this is the route I'm guessing is more correct, I need to figure out how to render a dynamic widget.
For Example: As I append and item to the dashItems collection, it should render a new widget (based on the name I've provided)
I have seen that I can swap out templates using ngInclude, but I'm still unclear as to how to get a template and controller to be injected dynamically. (EG all my templates wont be sharing a common controller).
JavaScript:
angular
.module('myDashboard', [])
.config(routesConfig)
.component('dashboard', {
templateUrl: 'dashboard/dashboard.tpl.html',
controller: dashboardController
})
.component('widgetPie', {
template: '<h3>Pie Graph</h3>',
controller: function($log) {
$log.info('widgetPie: loaded');
}
})
.component('widgetLine', {
template: '<h3>Line Graph</h3>',
controller: function($log) {
$log.info('WidgetLine: loaded');
}
});
function routesConfig($stateProvider) {
// this is only needed if I go the ui-view route.. I assume
$stateProvider
.state('widgetPie', { component: 'widgetPie'})
.state('widgetLine', { component: 'widgetLine'});
}
function dashboardController($log) {
$log.info('in dashboard');
this.dashItems = [
{ widget:'widgetPie' },
{ widget:'widgetLine' }
];
}
Markup (dashboard/dashboard.tpl.html):
<div>
<ul>
<li ng-repeat="item in dashItems">
<!--somehow render dynamic-->
<!--<widget-pie></widget-pie>-->
<!--<div ui-view="item.widget"></div>-->
</li>
</ul>
</div>
Question(s):
1.
I've looked into ngInclude, but to be perfectly honest, I'm not sure how to go about using it in this instance, and IF it is the right tool for this, or am I approaching this incorrectly?
2.
Should I even be adding items to the state provider for this, EG i / could a widget be seen as a child state (thus I'm not sure what would be seen as best practice)
I ended up changing the dashboard.tpl.html file to:
<div>
<ul>
<li ng-repeat="item in dashItems">
<div ng-include="item.widget"></div>
</li>
</ul>
</div>
But I also needed to add a build task to run through my widgets folder and generate the following (or you can manually add, whatever floats your boat I guess).
angular
.module('myDashboard')
.run(function ($templateCache) {
$templateCache.put('widgetPie', '<widget-pie></widget-pie>');
$templateCache.put('widgetLine', '<widget-line></widget-line>');
});
The above allows me to either use templateUrl, or inline templates.
.component('widgetPie', {
templateUrl: 'dashboard/widgetPie.tpl.html',
controller: function($log) {
$log.info('widgetPie: loaded');
}
})
.component('widgetLine', {
template: '<h1>Some Content</h1>',
controller: function($log) {
$log.info('widgetLine: loaded');
}
})
You can do it. Firstly, you need to use wrapper component which helps you compile your dynamic component:
app.component('dynamicWrapper',
{
controller: function widgetClientCtrl($scope, $compile, $element) {
var self = this;
self.$onInit = function () {
renderWidget(self.name, self.payload);
};
function renderWidget(name, payload) {
var template = '<' + name;
if (payload) {
$scope.payload = payload;
template += ' payload="payload"';
}
template += '></' + name + '>';
$element.append($compile(template)($scope));
}
},
bindings: {
name: '#',
payload: '=?'
}
});
your dynamic component:
app.component('someDynamicComponent', {
templateUrl: 'yourPath',
controller: dynamicComponentCtrl,
controllerAs: 'vm',
bindings: {
payload: '<'
}
});
and then:
<li ng-repeat="(name, payload) in vm.dynamicComponents">
<dynamic-wrapper name="{{name}}" payload="payload"></dynamic-wrapper>
</li>

Injecting resolves into directive controllers

I'm using AngularUI router (0.2.13) and have a state defined as such
.state('foo', {
template:'<div some-directive></div>',
resolve: {
foo: function(SomeService) {
return SomeService.something().promise;
}
}
})
and a directive like this:
app.directive('someDirective', function(){
return {
controller: function(data) {
// I want `data` to be injected from the resolve...
// as it would if this was a "standalone" controller
}
}
})
but this doesn't work - the data parameter causes an UnknownProvider error. Defining the directive controller independently and setting it by name in the directive has the same result.
I more or less get why this is happening, but have two questions:
is there a way to do what I'm trying to do?
should I be trying to do it, or have I slipped into an antipattern here?
You can't use resolves in directives, but you can pass the result resolved in the state down to the directive which I think accomplishes what you're looking for.
You'd want to update your state definition to include a controller and set a parameter on the directive:
.state('foo', {
template:'Test<div some-directive something="foo"></div>',
url: 'foo',
resolve:{
foo:function(SomeService){
return SomeService.something();
}
},
controller: function($scope, foo){
$scope.foo = foo;
}
})
Then update the directive to use this parameter:
.directive('someDirective', function(){
return {
controller: function($scope) {
// I want `data` to be injected from the resolve...
// as it would if this was a "standalone" controller
console.log('$scope.something: '+ $scope.something);
},
scope: {
something: '='
}
};
})
Here's a sample plunker: http://plnkr.co/edit/TOPMLUXc7GhXTeYL0IFj?p=preview
They simplified the api. See this thread:
https://github.com/angular-ui/ui-router/issues/2664#issuecomment-204593098
in 0.2.19 we are adding $resolve to the $scope, allowing you to do "route to component template" style
template: <my-directive input="$resolve.simpleObj"></my-directive>,

Modify views of a UI Router template

I'm trying to inject default data into a ui-view template.
$stateProvider.state('foo', {
template: '<div ui-view=bar></div>',
abstract: true,
views: {
bar: {
template: 'test'
}
}
});
$stateProvider.state('foo.whiz', {
// ...
});
This example doesn't work, but I hope it's enough to show you what I mean. So what I'm basically trying to do is, when I enter the state foo.whiz, which has a parent of foo, I'd like foo to inject default data into the bar ui-view. Currently it only seems you can populate named views via the child state (bar#foo)? How can I, essentially, initialize data via the route object?
I would say, there are 4 ways (maybe more) how to pass some stuff from child to parent. There is a working plunker.
Let's have these anchors/targets in the index.html:
<div ui-view="title"></div>
<div ui-view="scopeInheritance"></div>
<div ui-view="factory"></div>
<div ui-view="content"></div>
And this would be the common def of parent state 'foo'
.state('foo', {
url: '/foo',
views: {
'title' : { template: '<h3>foo - parent state title</h3>',},
'scopeInheritance' :
{
template: '<h3 ui-view="inheritance">{{parentModel.greeting}}</h3>',
controller: 'parentCtrl',
},
'factory' : { template: '<h3>{{dataHolder.message}}</h3>',},
'content' : {
template: '<div >Content of foo (parent) <ui-view /></div>',
},
},
})
I. child view replaces parent view completely
In this case, we will define different content for the view 'title' in our child state:
.state('foo.whiz', {
views: {
'title#' : {
template: '<h3>{{resolved}} - child state title</h3>',
controller : 'titleCtrl',
resolve : { resolvedData : function() { return "whiz"; }, },
},
...
So, we are using Absolute Names 'title#' to place our view into root template.
II. scope inheritance, child can fill properties of parents $scope.parentModel reference
.state('foo.whiz', {
views: {
'inheritance' :
{
controller: 'childCtrl',
},
...
Here we declare some model in parent $scope, and due to Scope Inheritance by View Hierarchy Only we can change these values in a child:
// parent declares model
.controller('parentCtrl', function($scope){
$scope.parentModel = { greeting : "Parent greets"};
...
// child changes value and does clean up
.controller('childCtrl', function($scope){
var remember = $scope.parentModel.greeting;
$scope.parentModel.greeting = "Child greets"
$scope.$on("$destroy", function (){$scope.parentModel.greeting = remember;});
...
III. factory as data holder
Simply let's inject some singleton, in angular world service/factory, as a holder of data into $rootScope
// singleton
.factory('dataHolder', function(){
return {};
});
// available everywhere
app.run(
['$rootScope', 'dataHolder',
function($rootScope, dataHolder) {
$rootScope.dataHolder = dataHolder;
...
// child can assign these, while parent will render them
.controller('childCtrl', function($scope){
$scope.dataHolder.message = "Child sent message"
Check that all here
IV. child view is nested in parent:
the most typical with UI-Router
// the anchor is inside of the parents view
.state('foo', {
views: {
'content' : { // the anchor for child
template: '<div >Content of foo (parent) <ui-view /></div>',
},
...
// child inject directily into parent
.state('foo.whiz', {
views: {
'' : {
template: '<h4>Content of whiz (Child)</h4>',
},
...
These are the basic ways how to pass data/view/messages with UI-Router (skipping the eventing system of angular at all)

Dynamically set the value of ui-sref Angularjs

I have searched for a similar question but the ones that came up seem slightly different.
I am trying to change the ui-sref='' of a link dynamically (this link points to the next section of a wizard form and the next section depends on the selection made on the dropdown list). I am simply trying to set the ui-sref attribute depending on some selection in a select box. I am able to change the ui-sref by binding to a scope attribute which is set when a selection is made. however the link does not work, is this possible at all? thanks
<a ui-sref="form.{{url}}" >Next Section</a>
and then in my controller, I set the url parameter this way
switch (option) {
case 'A': {
$scope.url = 'sectionA';
} break;
case 'B': {
$scope.url = 'sectionB';
} break;
}
Alternatively, I used directives to get it to work by generating the hyperlink with the desired ui-sref attribute according to the option selected on the select box (drop down).
Hhowever this means I have to re-create the link each time a different option is selected from the selectbox which causes an undesirable flickering effect.
My question is this, is it possible to change the value of the ui-sref as I tried doing above by simpling changing the value of url in my controller or do I have to re-create the entire element using a directive each time a selection is made as I have done below? (just showing this for completeness)
Select option directive (this directive generates the link directive)
define(['app/js/modules/app', 'app/js/directives/hyperLink'], function (app) {
app.directive('selectUsage', function ($compile) {
function createLink(scope,element) {
var newElm = angular.element('<hyper-link></hyper-link>');
var el = $(element).find('.navLink');
$(el).html(newElm);
$compile(newElm)(scope);
}
return {
restrict: 'E',
templateUrl: '/Client/app/templates/directives/select.html'
,link: function (scope, element, attrs) {
createLink(scope, element);
element.on('change', function () {
createLink(scope,element);
})
}
}
})
Hyperlink directive
define(['app/js/modules/app'], function (app) {
app.directive('hyperLink', function () {
return {
restrict: 'E',
templateUrl: '/Client/app/templates/directives/hyperLink.html',
link: function (scope, element, attrs) { }
}
})
Hyperlink template
<div>
<button ui-sref="form.{url}}">Next Section</button>
</div>
Looks like this is possible to do after all.
A breadcrumb on GitHub by one of the ui-router authors led me to try the following:
<a ng-href="{{getLinkUrl()}}">Dynamic Link</a>
Then, in your controller:
$scope.getLinkUrl = function(){
return $state.href('state-name', {someParam: $scope.someValue});
};
Turns out, this works like a charm w/ changing scoped values and all. You can even make the 'state-name' string constant reference a scoped value and that will update the href in the view as well :-)
There is a working plunker. The most easier way seems to be to use combination of:
$state.href() (doc here) and
ng-href (doc here)
These together could be used as:
<a ng-href="{{$state.href(myStateName, myParams)}}">
So, (following this plunker) having states like these:
$stateProvider
.state('home', {
url: "/home",
...
})
.state('parent', {
url: "/parent?param",
...
})
.state('parent.child', {
url: "/child",
...
We can change these values to dynamically generate the href
<input ng-model="myStateName" />
<input ng-model="myParams.param" />
Check it in action here
ORIGINAL:
There is a working example how to achieve what we need. But not with dynamic ui-sref .
As we can can check here: https://github.com/angular-ui/ui-router/issues/395
Q: [A]re dynamic ui-sref attributes not supported?
A: Correct.
But we can use different feature of ui-router: [$state.go("statename")][5]
So, this could be the controller stuff:
$scope.items = [
{label : 'first', url: 'first'},
{label : 'second', url: 'second'},
{label : 'third', url: 'third'},
];
$scope.selected = $scope.items[0];
$scope.gotoSelected = function(){
$state.go("form." + $scope.selected.url);
};
And here is the HTML template:
<div>
choose the url:
<select
ng-model="selected"
ng-options="item.label for item in items"
></select>
<pre>{{selected | json}}</pre>
<br />
go to selected
<button ng-click="gotoSelected()">here</button>
<hr />
<div ui-view=""></div>
</div>
The working example
NOTE: there is more up to date link to $state.go definition, but the deprecated one is a bit more clear to me
Take a look in this issue #2944.
The ui-sref doesn't watch the state expression, you can use ui-state and ui-state-params passing the variable.
<a data-ui-state="selectedState.state" data-ui-state-params="{'myParam':aMyParam}">
Link to page {{selectedState.name}} with myParam = {{aMyParam}}
</a>
Also a quickly demo provided in the ticket.
I managed to implement it this way (I'm using the controllerAs variant though - not via $scope).
Template
<button ui-sref="main({ i18n: '{{ ctrlAs.locale }}' })">Home</button>
Controller
var vm = this;
vm.locale = 'en'; // or whatever dynamic value you prepare
Also see the documentation to ui-sref where you can pass params:
https://github.com/angular-ui/ui-router/wiki/Quick-Reference#ui-sref
After trying various solutions I found the problem in the angular.ui.router code.
The problem comes from the fact that ui.router update method is triggered with the ref.state which means that it is not possible to update the value of the href used when the element is clicked.
Here are 2 solutions to solve the problem:
Custom Directive
module.directive('dynamicSref', function () {
return {
restrict: 'A',
scope: {
state: '#dynamicSref',
params: '=?dynamicSrefParams'
},
link: function ($scope, $element) {
var updateHref = function () {
if ($scope.state) {
var href = $rootScope.$state.href($scope.state, $scope.params);
$element.attr('href', href);
}
};
$scope.$watch('state', function (newValue, oldValue) {
if (newValue !== oldValue) {
updateHref();
}
});
$scope.$watch('params', function (newValue, oldValue) {
if (newValue !== oldValue) {
updateHref();
}
});
updateHref();
}
};
});
The HTML to use it is quite simple :
<a dynamic-sref="home.mystate"
dynamic-sref-params="{ param1 : scopeParam }">
Link
</a>
Fix ui.router code :
In angular.router.js your will find the directive $StateRefDirective (line 4238 for version 0.3).
Change the directive code to :
function $StateRefDirective($state, $timeout) {
return {
restrict: 'A',
require: ['?^uiSrefActive', '?^uiSrefActiveEq'],
link: function (scope, element, attrs, uiSrefActive) {
var ref = parseStateRef(attrs.uiSref, $state.current.name);
var def = { state: ref.state, href: null, params: null };
var type = getTypeInfo(element);
var active = uiSrefActive[1] || uiSrefActive[0];
var unlinkInfoFn = null;
var hookFn;
def.options = extend(defaultOpts(element, $state), attrs.uiSrefOpts ? scope.$eval(attrs.uiSrefOpts) : {});
var update = function (val) {
if (val) def.params = angular.copy(val);
def.href = $state.href(ref.state, def.params, def.options);
if (unlinkInfoFn) unlinkInfoFn();
if (active) unlinkInfoFn = active.$$addStateInfo(ref.state, def.params);
if (def.href !== null) attrs.$set(type.attr, def.href);
};
if (ref.paramExpr) {
scope.$watch(ref.paramExpr, function (val) { if (val !== def.params) update(val); }, true);
def.params = angular.copy(scope.$eval(ref.paramExpr));
}
// START CUSTOM CODE : Ability to have a 2 way binding on ui-sref directive
if (typeof attrs.uiSrefDynamic !== "undefined") {
attrs.$observe('uiSref', function (val) {
update(val);
if (val) {
var state = val.split('(')[0];
def.state = state;
$(element).attr('href', $state.href(def.state, def.params, def.options));
}
});
}
// END OF CUSTOM CODE
update();
if (!type.clickable) return;
hookFn = clickHook(element, $state, $timeout, type, function () { return def; });
element.bind("click", hookFn);
scope.$on('$destroy', function () {
element.unbind("click", hookFn);
});
}
};
}
Came to answer that for good :)
Fortunately, you don't need to use a button for ng-click, or use a function inside an ng-href to achieve what you seek. Instead;
You can create a $scope var in your controller and assign the ui-sref string in it and use it in your view, as ui-sref attribute.
Like this:
// Controller.js
// if you have nasted states, change the index [0] as needed.
// I'm getting the first level state after the root by index [0].
// You can get the child by index [1], and grandchild by [2]
// (if the current state is a child or grandchild, of course).
var page = $state.current.name.split('.')[0];
$scope.goToAddState = page + ".add";
// View.html
<a ui-sref="{{goToAddState}}">Add Button</a>
That works perfectly for me.
The best approach is to make use of uiRouter's $state.go('stateName', {params}) on button's ng-click directive. And disable the button if no option is selected.
HTML
<select ng-model="selected" ng-options="option as option.text for option in options"></select>
<button ng-disabled="!selected" type="button" ng-click="ctrl.next()">Next</button>
Controller
function Controller($scope, $state){
this.options = [{
text: 'Option One',
state: 'app.one',
params: {
param1: 'a',
param2: 'b'
}
},{
text: 'Option Two',
state: 'app.two',
params: {
param1: 'c',
param2: 'd'
}
},{
text: 'Option Three',
state: 'app.three',
params: {
param1: 'e',
param2: 'f'
}
}];
this.next = function(){
if(scope.selected){
$state.go($scope.selected.state, $scope.selected.params || {});
}
};
}
State
$stateProvider.state('wizard', {
url: '/wizard/:param1/:param2', // or '/wizard?param1&param2'
templateUrl: 'wizard.html',
controller: 'Controller as ctrl'
});
<a ng-click="{{getLinkUrl({someParam: someValue})}}">Dynamic Link</a>
$scope.getLinkUrl = function(value){
$state.go('My.State',{someParam:value});
}
It returns an object
this is just working for me
in controller
$scope.createState = 'stateName';
in view
ui-sref="{{ createState }}"
For manage multiple dynamic params using ui-sref, here my solution :
Html : ('MyPage.html')
<button type="button" ui-sref="myState(configParams())">
Controller : ('MyCtrl')
.controller('MyCtrl', function ($scope) {
$scope.params = {};
$scope.configParams = function() {
$scope.params.param1 = 'something';
$scope.params.param2 = 'oh again?';
$scope.params.param3 = 'yes more and more!';
//etc ...
return $scope.params;
};
}
stateProvider : ('myState')
$stateProvider
.state('myState', {
url: '/name/subname?param1&param2&param3',
templateUrl: 'MyPage.html',
controller: 'MyCtrl'
});
Enjoy !
<ul class="dropdown-menu">
<li ng-repeat="myPair in vm.Pairs track by $index">
<a ui-sref="buy({myPairIndex:$index})">
<span class="hidden-sm">{{myPair.pair}}</span>
</a>
</li>
</ul>
If someone only wants to dynamically set the $stateParams of ui-sref in Angularjs.
Note: In inspect element it will still appear as "buy({myPairIndex:$index})" but $index will be fetched in that state.
I found this solution the most appropriate:
.state('history', {
url: 'home',
controller: 'homeCtrl',
templateUrl: "home.html"
})
.state('settings', {
url: "/settings",
controller: 'settingsCtrl',
templateUrl: 'settings.html'
})
<button ui-sref="{{$ctrl.newProductLink()}}"</button>
ctrl.newProductLink = () => a > b ? 'home' : 'settings';
Or just something like this:
<a ui-sref="{{ condition ? 'stateA' : 'stateB'}}">
Link
</a>

In AngularJs, how do I identify a specific scope within a controller that is used multiple times on a page?

I have an application that uses multiple tabs with a grid in each tab. I have multiple pages using this same configuration.
I have a single controller that works for all grids. I would like to reuse this controller throughtout my application.
My problem is that I am trying to lazy load the data for each tab until the tab is clicked. What I can't figure out is how to tell the controller which scope belongs to which tab and ONLY get the data for that grid. I know internally AngularJs does this because if I just load all the data for each tab at once I can click on my paginator, search, etc. for each tab and only that scope is updated.
I have ng-click setup for each tab and I can get my controller to fire when a tab is clicked. However, the controller calls all instantiated scopes to load data for their respective grids.
My approach may not be the best but it seems silly to create seperate controllers that do exactly the same thing.
Note: Angular UI tabs with bootstrap is not an option.
My view
<div ng-app="TabsApp">
<div tabs>
<div class="tabs">
<a ng-click="clickTab(0)" ng-class="{selected: selectedTab==0}">Localized Text From Server</a>
<a ng-click="clickTab(1)" ng-class="{selected: selectedTab==1}">Localized Text From Server</a>
<a ng-click="clickTab(2)" ng-class="{selected: selectedTab==2}">Localized Text From Server</a>
</div>
<div class="tab-content">
<div ng-show="selectedTab==0" ng-init="init(#Model.UserId, 'useraddresses')" ng-controller="ListCtrl">#Html.Partial("_Grid0")</div>
<div ng-show="selectedTab==1" ng-init="init(#Model.UserId, 'userphones')" ng-controller="ListCtrl">#Html.Partial("_Grid1")</div>
<div ng-show="selectedTab==2" ng-init="init(#Model.UserId, 'usernotes')" ng-controller="ListCtrl">#Html.Partial("_Grid2")</div>
</div>
</div>
</div>
My app and factory url
var TabsApp = angular.module("TabsApp", ['ngResource', 'ngRoute']);
TabsApp.factory('Api', function($resource){
return $resource('/api/user/:userId/:ctrl', { userId: '#userId', ctrl: '#ctrl'});
});
My controller - child scope(s)
var ListCtrl = function ($scope, $location, Api){
$scope.search = function () {
Api.get({
userId: $scope.userId,
ctrl: $scope.ctrl,
q: $scope.query
//etc.
},
function(data){
$scope.items = data.items;
//other mapping
});
};
$scope.init = function(userId, ctrl){
$scope.userId = userId;
$scope.ctrl = ctrl;
};
$scope.reset = function(){
$scope.items = [];
$scope.search();
};
//kind of a hack but broadcasting was the only way I could figure out to listen for tab changes
$scope.tabModelChange = { 'isChanged': false };
$scope.$on('tabModelChange', function(event, args) {
$scope.tabModelChange.isChanged = true;
var activeTab = args.val[$scope.selectedTab];
if (!activeTab.isLoaded) {
$scope.reset();
}
});
//filtering, sorting, pagination, etc.
};
My directive: Parent scope
TabsApp.directive('tabs', function () {
return {
controller: function ($scope, $element) {
$scope.selectedTab = 0;
$scope.tabModel = [];
//I use localized text and any number of tabs on my views from the server so the client wont know how many tabs each view will have
var tabs = angular.element($element.children()[1]).children();
angular.forEach(tabs, function (value, key) {
$scope.tabModel.push({'isLoaded' : false, 'isActive': false});
});
$scope.clickTab = function(tab) {
$scope.selectedTab = tab;
$scope.tabModel[$scope.selectedTab].isActive = true;
};
$scope.$watch('tabModel', function(newVal, oldVal) {
if (newVal !== oldVal) {
$scope.$broadcast('tabModelChange', { 'val': newVal });
}
}, true);
}
};
});
I suspect that when my controller receives a broadcast that the tab model has changed it calls $scope.reset with $scope being a parent scope which then traverses the child scopes looking for 'reset'. Because there are 3 instances of ListCtrl, parent scope finds 3 child scopes with 'reset'. Hence, all the data gets loaded at once.
How can I get $scope.reset to match the tab that was clicked? Thanks.
With minimal change to your existing code, here's a plunker that does what you want, I think.
http://plnkr.co/edit/TVwWQehgWJay7ngA8g6B?p=preview
Basically I made a second directive called tab that accepts an argument which is a function to evaluate when that tab is switched to.
TabsApp.directive('tab', function () {
return {
require: '^tabs',
link: function (scope, element, attrs, tabsCtrl) {
tabsCtrl.add({
'isLoaded' : false,
'isActive': false,
'changed': function () { scope.$eval(attrs.tab); }
});
}
};
});
Here's what we do (i'm a bit lazy and going to pull use our code), We load when tab is clicked.
Here's the tabs
<ul class='nav nav-pills'>
<li ng-class="{ active : mode == 'incomplete'}"><a tabindex="-1" href="#/incomplete" ng-click='mode="incomplete"'>Incomplete orders</span></a></li>
<li ng-class="{ active : mode == 'completed'}"><a tabindex="-1" href="#/completed" ng-click='mode="completed"'>Completed orders</a></li>
</ul>
We setup routes
.config(['$routeProvider', '$env', function ($routeProvider, $env) {
$routeProvider.when("/completed", {
templateUrl: $env.urlRoot + "content/partials/orders/completed.htm", controller: 'CompletedOrdersCtrl'
});
$routeProvider.when("/incomplete", {
templateUrl: $env.urlRoot + "content/partials/orders/incomplete.htm", controller: 'IncompleteOrdersCtrl'
});
$routeProvider.otherwise({
templateUrl: $env.urlRoot + "content/partials/orders/incomplete.htm", controller: 'IncompleteOrdersCtrl'
});
} ]);
We then have the two controllers IncompleteOrdersCtrl and CompleteOrdersCtrl that call the service to get the correct data. You could possibly consolidate into one controller with a parameter passed in the route config.
Hopefully this works for you
--dan

Resources