This is a trivia for many, but I never worked with angular before and I need help to achieve a simple task, cause I'm stucked.
My ng-controller processctrl has a load() method declared.
My view has two calls to ng-controller="processoctrl", causing load() method to run twice. processctrl also has a property currentPhase that starts null and load() set its value. I could just write ($scope.currentPhase || load()) to prevent double load(), but the bind to currentPhase occurs atop (in DOM) of the repeater.
I could place an object property into the $rootScope and update it from processoctrl.scope.load(), but it would turn into a madness.
I could simply $($(".headerCurrentPhase")[0]).html($scope.currentPhase) into my load() method, but it's insanity too.
I could rewrite load() to getProcesses = function(){} and $rootScope.$emit('getProcesses',{}) elsewhere and into the controller $rootScope.$on('getProcesses',getProcesses) to prevent this double load(), but I think this is redundate, so how to simply call a controller function instead of load()? Or how to achieve this simple task? Use a directive?
view:
<div ng-include src="'includes/overview-header.html'"></div>
<div ng-include src="'includes/process-info.html'"></div>
excerpt of includes/overview-header.html :
<div class="col-md-12">
....
<h4><strong>currentPhase</strong></h4>
<p ng-controller="processoctrl">
<span class="label label-primary headerCurrentPhase" ng-bind="currentPhase"></span>
</p>
</div>
excerpt of includes/process-info.html :
<tbody ng-controller="processctrl">
<tr ng-repeat="Process in Processes|orderBy:'ID'">
<td>{{Process.isCurrent}}</td>
<td>{{Process.isCurrent}}</td>
<td>{{Process.ID}}</td>
<td>{{Process.Title}}</a></td>
</tr>
</tbody>
processoctrl
(function (app) {
app.controller('processctrl', function ($scope, $rootScope, $routeParams, factorysvc) {
$scope.Processes = [];
$scope.currentPhase = null;
load();
//($scope.currentPhase || load())
function load() {
var promiseGet = factorysvc.getPsby($routeParams.itemId);
promiseGet.then(function (data) {
$scope.Processes = data;
$rootScope.root.empreendimento.currentPhase = $scope.currentPhase = data[0].currentPhase.Title;
}, function (err) {
$scope.Message = "Error " + err.status;
});
}
})}(angular.module('sgaapp')));
Man, I didn't catch what are you want to do, but I want to take you few advise:
Your logic it's really complicated, you should re-think it, cause now it's sucks;
Includes in angular sucks too. Really. You shouldn't use it at all. How to avoid it? Well, there is two option.
First is to use client-side routing (for pages); Instead of using built-in routing, better to use ui-router. You will be able to use nested states and routes;
Second is to use directives instead of includes. Really, just imagine - header or footer, both can be directives, which you just include in pages when it's needed.
What for me - I use first and second solution in same time.
You should avoid to put anything in $rootScope. Perhaps it's couldn't kill you, and from time to time it's the only solution, but, u know, it's a bad practice.
If you need to store data and share it between controllers, the better option is to us factories (or other services, but factories most universal).
Example:
app.factory('MyFactory', function () {
return {
myData: 'some default value',
someCommonFuction: function (a, b) {
return a + b;
}
};
});
As well as data-store, you can use factory as utility-class, with common functions
Is there any real need to have 2 includes?
If overview-header is shared across views, then we can have currentPhase at $rootScope level or having a parent level controller and have it.
If overview-header is only for this view, then we can combine both htmls into one and have processctrl at wrapped tag.
Related
I have an angular filter which is formatting its output by the value of some global variable. That one can change asynchronously, and so do the result of the filter. However, the result is not rendered into view. Not event dispatching $apply() on $scope or even $rootScope, would force view to render the new output of this filter.
For me, it seems like that since filters input stays the same for the whole time, angular won't call filters own method (sort of optimisation there, I guess).
Here is a plunker with the minimal demonstration:
https://plnkr.co/edit/aUm0XRzWn1qr8ZFZB2sY?p=preview
There is a workaround by encapsulating filter inside method (on controllers or $scope) which would render in each time view redraws. But it forces me to redeclaring this method in every controller on view that require use of this filter.
Did anyone also encounter this limitation with filters?
This is happening in every angular version I've tested from 1.2.x to 1.5.x
The problem can be solved with a stateful filter.
From the Docs:
Stateful filters
It is strongly discouraged to write filters that are stateful, because the execution of those can't be optimized by Angular, which often leads to performance issues. Many stateful filters can be converted into stateless filters just by exposing the hidden state as a model and turning it into an argument for the filter.
If you however do need to write a stateful filter, you have to mark the filter as $stateful, which means that it will be executed one or more times during the each $digest cycle.
--AngularJS Developer Guide -- Stateful filters
JS
app.filter('myFilter', function() {
function myFilter() {
return someValue
}
//Set filter to $stateful
myFilter.$stateful = true;
//
return myFilter;
})
The DEMO on PLNKR
Or if you don't want to make the filter stateful you can make use of a service
that you inject in both filter and controller. (Which is pretty much standard in angular when you want to share any data between anything and anything)
https://plnkr.co/edit/GUth5licV3ubLUf2maTV?p=preview
.factory('testService', function(){
return {
someValue : 'This is not right',
update : function(str){ this.someValue = str; }
};
})
.filter('myFilter', function( testService ) {
// Your problem is that this function "lives on" the way it was created
return function() {
return testService.someValue;
}
})
.controller('myController', function($scope, $filter, testService) {
testService.update( 'This is OK');
$scope.myMethodCallingMyFilter = function() {
return $filter('myFilter')();
};
})
Following situation:
There are two directives with controllers (A & B) - which are both children of another controller (C).
Controller A manages stuff for its model.
Now Controller C needs to call functions from Controller A to modify some stuff. And furthermore it needs access to some properties and read them.
I'm uncertain on what the right approach to communication is. And wheter to stick to one approach.
Following up is a small code example to illustrate the problem in a more concrete way.
First off there is a provider where components can register themselfes.
angular.module('components', [])
.provider('db', function(){
this.registerComponent = function(name, component){
...
}
});
Now there is a directive & a Controller (A) which displays concrete components.
angular.module('components')
.directive('componentDashboard', function(){
return {
scope:{
concreteComponents: '='
},
controller: function($scope){
$scope.model = concreteComponents;
$scope.model.someImportantProp = "foo";
$scope.addComponent = function(c){...}
}
}
})
That is basically the setup. The directive componentDashboard can display a set of components which registered to the db provider.
There are controller functions (A) like addComponent which needs to be called from outside of the controller (Controller B wants to call this). Furthermore Controller B wants to access different properties and so on.
Whats the preferred way of doing this?
At the moment there are these paradigmas used:
1) Factory hack ?! Basically there is a factory with some functions:
angular.module('components')
.factory('componentStub', function($log){
return {
addComponent : function(c){
$log.error("stub not overwritten");
}
}
})
These functions are now overwritten by the component's directive:
angular.module('components')
.directive('componentDashboard', function(componentStub){
return {
scope:{
concreteComponents: '='
},
controller: function($scope){
$scope.model = concreteComponents;
$scope.model.someImportantProp = "foo";
$scope.addComponent = function(c){...}
componentStub.addComponent = function(c){
$scope.addComponent(c);
}
}
}
})
2) event based
angular.module('components')
.factory('notificationCenter', function(){
return {
registerToNotification: function(id, not, cb){..}
}
})
.directive('componentDashboard', function(notificationCenter){
return {
scope:{
concreteComponents: '='
},
controller: function($scope){
$scope.model = concreteComponents;
$scope.model.someImportantProp = "foo";
$scope.addComponent = function(c){...}
notificationCenter.registerToNotification("foo", "doAddComponent", function(c){
$scope.addComponent(c)
}
}
}
})
At the moment there are both approaches used. There are some advantages of using this. Its developed fast, there are just few depencencies or restrictions to it. And it works.
But! I'm unsure about whether it is a good approach. I'm experiencing kinda bad maintenance on that and the more complex it gets(lets say some components can be added and some not -> states) the more it feels like not the right approach.
How should this problem be approached ?
Sorry for the long question, thanks in advice
Schemii
There are multiple methods how you can solve this. Ensuring that it is maintainable comes down to isolation: let the different components work on their own without dependencies of each other.
Combine any of the following tactics (from easiest to harder to implement):
Let the parent view pass the model (or parts of it) to child directives (via attributes)
$watch for changes in the main controller/directive to react on changes.
Let the parent controller/directive register ($scope.$on) callbacks to certain events. Childs can $emit events upwards to their parents. Parents can $broadcast events downwards to their children.
Let the child directive expose a callback/expression (see the & prefix in the isolate scope options)
Share a (singleton) service instance that will handle model changes. Inject this service where needed. Since this is a singleton, you'll have to make sure that you'll cleanup callbacks whenever a scope is destroyed otherwise you'll leak memory.
Hope this helps.
Cheers.
I'm building a small two-language app with the use of angular-translate. I want to have a language switcher in every view (controller). I'm trying to figure out how to put the code responsible for language switching into every controller. The code looks like this:
var langSwitch = $Scope.setLang = function (langKey) {
$translate.use(langKey);
};
So far I've figured that I can create a factory that looks like this:
app.factory('langSwitch', function ($rootScope, $translate) {
var langSwitch = $rootScope.setLang = function (langKey) {
$translate.use(langKey);
};
return langSwitch;
});
and inject it into controllers in this maner:
app.controller('HomeCtrl', function (langSwitch) {
// normal controller code here
});
This works but 1) I'm using $rootScope and I have a feeling this is bad practice & 2) jsHint screams that "langSwitch" is not defined. Maybe there is a simpler way to make the function global without putting it into every controller?
I'm still pretty new to Angular so don't scream at me :) Thanks.
edit
My view:
<button ng-click="setLang('en_GB')">English</button>
<button ng-click="setLang('pl_PL')">Polish</button>
Although you got the idea, you overcomplicated things a bit. You could declare the service as follows:
app.service('langSwitch', function ($translate) {
this.setLang = function (langKey) {
$translate.use(langKey);
};
});
And then inject langSwitch in the controller responsible for lang switching, as you already did. No need to inject $rootScope in the service.
You don't need $rootScope indeed unless you need to process some global events in your application. All services and factories in angular are singletons by default. That means once it created, it will be passed as the same instance in every place it is declared as a dependency. So if you want to share data and functionality between different controllers - the services will suit fine. You can change your factory code to:
app.factory('langSwitch', function($translate) {
return {
setLang: function(langKey) {
$trasnlate.use(langKey);
};
};
});
I have page with two tables. I need to make some actions with both tables, triggered outside: some functions in view controller.
So, both tables has same methods and variables and I should reload or search both tables with button and input in view controller. This is it.
What I want is to separate tables - I dont want to call reloadTable1 and reloadTable2, and use table1Data and table2Data in scope to render it.
First idea was to create service for each table. But the problem was data rendering, when I use just variable to render it.
Here is example: http://plnkr.co/edit/bzgvtFzRWUDad1CR4mZV?p=preview . Not really related, but question: why it doesnt work? I know workaround - use accessors, but I dont really want to create function per variable! I want to use same class for 2 services, but with difference - one argument.
Second idea to use 3 controllers: parent and 1 per each table, reload tables with $scope.$broadcast. But I don't really like this idea: it uses events(I don't think it is good idea to use it in this case) and it will use additional controllers. Also, I think, it is good idea to use service here.
Any advices?
What I normally do if I need to share data between controllers, I use a service.
I modified your Plunker to demostrate
Example:
<div ng-controller="main">
{{service.data | json }}
<button ng-click="service.query()">refresh data</button>
</div>
<div ng-controller="controller2">
{{service.data | json }}
</div>
Controllers and service:
app.controller('controller1', function($scope, myservice) {
$scope.service = myservice;
});
app.controller('controller2', function($scope, myservice) {
$scope.service = myservice;
});
app.service('myservice', function() {
return {
data: [],
query: function() {
for (var i in [1, 2, 3]) {
this.data.push(Math.floor((Math.random() * 100) + 1));
}
}
}
});
You could also use $scope.$broadcast. Or if you want to generate the event from inside the service use $rootScope.$broadcast (you need to inject $rootScope)
I'm just starting to play with angularJS, so maybe I'm asking something easy to do, but I can't find the way to do it.
The situation is the following: I have a list that's populated by an ng-repeat taking the values from a scoped controller variable. This variable is loaded on page load by an jsonp call, and this works fine.
The problem comes when I need to reload this list based on another select. For example, if a select 'day' value in the select I need to show some values and when I select 'week' I need to show others (also loaded via ajax).
What I've tried is to have a service that loads the data and returns it, and in the controller have two methods, one for the first load and another for the second one that does $scope.apply with the variable. I then call this second method on select value change (I've done it with jquery to simplify it until I can fix this).
This is part of my HTML
<div x-ng-controller='LeaderboardCtrl'>
<select id='leaderboard-select'>
<option value='day'>day</option>
<option value='week'>week</option>
<option value='month'>month</option>
</select>
<div x-ng-repeat='p in leaderboard'>
<p>{{p}}</p>
</div>
</div>
And this is part of the code that affects this functionality
var lead = angular.module("lead",[]);
function LeaderboardCtrl($scope,$attrs,$http,jtlanService) {
$scope.leaderboard = [];
$scope.period = 'day';
var data = {
period:$scope.period
};
$scope.loadLeaderboard = function(){
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
}
$scope.reloadLeaderboard = function() {
myService.loadLeaderboard(data).then(function(leaderboard) {
$scope.$apply(function() {
$scope.leaderboard = [];
$scope.leaderboard.push.apply($scope.leaderboard,leaderboard);
});
});
}
$scope.loadLeaderboard()
}
lead.service("myService",["$http", function($http) {
var myService = {
loadLeaderboard : function(data) {
var promise = $http.jsonp("/widget/leaderboardJSONP?callback=JSON_CALLBACK&_="+new Date(),{
params:data,
cache:false,
ajaxOptions: { cache: false }
}).then(function(response) {
return response.data;
});
return promise;
}
};
return myService;
}]);
$("#leaderboard-select").change(function(){
scope.period = $("#leaderboard-select").val();
scope.reloadLeaderboard();
});
Here's a fiddle with the code: http://jsfiddle.net/WFGqN/3/
Your fiddle is riddled with issues:
There's no ng-app in your mark-up
You need to change the second Framework Extensions dropdown to one of the "No wrap" options
Your service needs to be defined above your controller
Your controller is referencing "jtlanService" but you've defined "myService"
Your $http.jsonp call isn't going to work as is, but you could use can use the echo service (see Ajax Requests on the left side) to emulate requests
You can't and shouldn't be using jQuery events to call Angular controllers. You should use ng-change and not $().change (and even if you were using jQuery for event binding, you should be using $().on('change')).
You didn't need to use $scope.$apply in your loadLeaderboard function, since when you're calling it, you were already inside of of an $apply call.
There's no need for 2 load+reload leaderboard methods.
And after all that, you don't actually need jQuery.
Here's a fiddle that fixes things up and I think gets you what you want: http://jsfiddle.net/WFGqN/5/. You'll of course need to fix the service on your end, but you get the idea.
I recommend reading this SO answer: "Thinking in AngularJS" if I have a jQuery background?