Angularjs directive, maintaining internal state for each of the directive instance - angularjs

I have a directive which has dependency on a service, I am maintaining some directive state in the service.
So whenever same directive is used in two places, it needs to instantiate the new service.
for example,
<body>
First directive instance: <custom-directive> 1 </custom-directive>
Second directive instance: <custom-directive> 2 </custom-directive>
</body>
In this case, both the controllers are populated with the same service object, but I need the different service objects to maintain their states internally.
Can someone help.

You can still use a service/factory, but expose a getInstance method inside your service that returns a new instance, allowing you to inject and subsequently create a new instance for each directive.
angular.module('app', []).factory('sharedService', sharedService);
function sharedService() {
return {
getInstance: function () {
return new instance();
}
}
function instance() {
// functionality
}
}

Related

Bind scope variable from controller to directives without using $watch

For Angular 1, I have an AJAX call that is made in the controller (as it is dependent on another set of data that is returned during the route resolve) and once the response is back, the data is passed down to directives. Since the data doesn't come back until after the directives are compiled, the directives initially gets an undefined for that data passed from the controller and would only get the data if I am watching that scope value inside each of the directive. Is there a better way where I don't have to use $scope.$watch or any event listeners, such as $broadcast/$on? I don't want to exhaust the digest cycle with too much watchers.
Here's a mock structure:
<parent>
<directive1 data="manipulateDataReturnedFromAJAXCall"></directive1>
</parent>
//template for directive1
<div>
<directive2 ng-if="data" attr1="data.field1"></directive2>
<div>
What about using a callback in your http service.
//Controller
MyHTTPService.getData(function(data){
$scope.manipulateDataReturnedFromAJAXCall = data;
})
//MyHTTPService service
return{
getData : function(fct){
$http.get("url/to/data").then(function(response){
fct(response.data);
})
}
}

AngularJS - How to pass data through nested (custom) directives from child to parent

I am looking to find the best way of sending scope through nested directives.
I have found that you can do $scope.$parent.value, but I understood that's not a best practice and should be avoided.
So my question is, if I have 4 nested directives like below, each with it's own controller where some data is being modified, what's the best way to access a value from directive4 (let's say $scope.valueFromDirective4) in directive1?
<directive1>
<directive2>
<directive3>
<directive4>
</directive4>
</directive3>
</directive2>
</directive1>
For the "presentational" / "dumb" components (directive3 and directive4), I think they should each take in a callback function which they can invoke with new data when they change:
scope: {
// Invoke this with new data
onChange: '&',
// Optional if you want to bind the data yourself and then call `onChange`
data: '='
}
Just pass the callback down from directive2 through directive4. This way directive3 and directive4 are decoupled from your app and reusable.
If they are form-like directives (similar to input etc), another option is to look into having them require ngModel and have them use ngModelController to update the parent and view. (Look up $render and $setViewValue for more info on this). This way you can use them like:
<directive4 ng-model="someObj.someProp" ng-change="someFunc()"></directive4>
When you do it like this, after the model is updated the ng-change function is automatically invoked.
For the "container" / "smart" directives (directive1 and directive2), you could also have directive2 take in the callback which is passed in from directive1. But since directive1 and directive2 can both know about your app, you could write a service which is injected and shared between directive1 and directive2.
Nested directives can always have an access to their parents' controllers via require. Let's say you want to change value from the directive1's scope from any of its nested directives. One of the possible ways to achieve that is to declare a setter in the directive1's controller setValue(value). Then in any of nested directives you need to require the directive1's controller and by doing that you'll get an access to the setter setValue(value) and other methods the controller provides.
angular
.module('yourModule')
.directive('directive1', function() {
return {
controller:['$scope', funciton($scope) {
return {
setValue: setValue
};
funciton setValue(value) {
$scope.value = value;
}
}]
// The rest of the directive1's configuration
};
})
.directive('directive4', function() {
return {
require: '^^directive1',
link: (scope, elem, attrs, directive1Ctrl) {
// Here you can call directive1Ctrl.setValue() directly
}
// The rest of the directive4's configuration
};
})
Another way is to $emit events from a child directive's controller whenever value is changed by the child. In this case the parent directive's controller should subscribe to that event and handle the data passed along with it.

Override controller functions in directive is a good idea?

I have a generic functionality implemented inside a controller. When i write a directive is it good idea to extend those controller functions inside the directive ?
Like in below implementation inside the link function.
var superCancel = scope.cancel;
// Overriding the cancel function from the controller
scope.cancel = function() {
if(element.hasClass('ng-dirty')){
element.removeClass("ng-dirty");
}
// Calling controller cancel
superCancel();
};
If your directive html is coming inside the controller in html then you can use $parent instead of rewriting
in directive:
$scope.$parent.cancel(); // only if controller coming as parent
If the controller is not coming as parent it's better to use a service or factory to implement that
Read here for more
It is better to have the directive use an attribute to set a parent scope value.
For example:
JS
app.directive("setModelApi", function() {
return {
require: "ngModel",
link: function(scope,elem,attrs, ngModelCtrl) {
scope.$eval(attrs.setModelApi, {$api: ngModelCtrl})
}
}
});
In the above example, the setModelApi directive evaluates the Angular Expression defined by the set-model-api attribute with $api exposing the ngModelController.
HTML
<input ng-model="x" set-model-api="xmodel=$api">
<button ng-click="xmodel.$setPristine()">Set Pristine</button>
The setModelApi directive sets the xmodel scope variable to the ng-model-controller API.
The button invokes the $setPristine method of the ng-model-controller API.
From the Docs:
$setPristine();
Sets the control to its pristine state.
This method can be called to remove the ng-dirty class and set the control to its pristine state (ng-pristine class). A model is considered to be pristine when the control has not been changed from when first compiled.
-- AngularJS ngModelController API Reference -- $setPristine
By using an HTML directive attribute to define the scope variable to which the API attaches, different inputs can use the directive and the connections are exposed in the HTML.
The DEMO on JSFiddle

AngularJS : difference between $rootScope.Controller and Service

I am trying to understand Angularjs behaviors.
I am building a web-app, and I want the CurrentUser's info be shared among all the app components. To do that, I have created a CurrentUserController bound to $rootScope. This controller is used by a user directive utilized in the body html element, so that it is globally accessible and it's created just one time.
app.controller('CurrentUserController', function ($rootScope)
{
// initialization
$rootScope.userCtrl = self; //<- MAKE IT GLOBAL
this.islogged=false;
this.name="";
var self = this;
// functions
this.isLogged = function()
{ return self.islogged; };
this.setLoggedIn = function(credentials)
{ self.islogged = true; };
this.setLoggedOut = function()
{ self.islogged = false; };
}
);
app.directive('currentUser', function() {
return {
controller:'CurrentUserController'
};
})
and then my html page
<html>
...
<body current-user>
...
</body>
</html>
However I read that Services should be used to share data between controllers, since they are singleton.
So my question is:
is my approach wrong, or it is equivalent as I utilized services?
Moreover, right now I can utilize the directive ng-switch calling $rootScope.userCtrl functions, like this:
<div id="nav-right-side" class="navbar-right" ng-switch on="userCtrl.isLogged()">
<div ng-switch-when="false">
<login-button></login-button>
</div>
<div ng-switch-when="true">
<loggedin-button></loggedin-button>
</div>
</div>
If I utilize services, would I still be able to do that?
Thank you
The $rootScope is indeed shared across all the app and it is also best to store models into services.
Why bother with services ?
Because of the $digest cycle. Each time a watched value is modified, the digest is triggered. In angular, by default the digest is a loop that goes down all your scope from the $rootScope down to its leafs. On each element, it has to get if the value has been modified or not to update the view accordingly. This is pretty expensive, and it is the cause of why angular can be slow on big applications. Keeping the scope as light as possible is how you can build complex apps in angular. That's why storing things is always better in services, you do not pollute the scope with data you could put somewhere else.
That being said, auth is peculiar because you want to access the same data from the view and services. You can store it in the $rootScope as Asta puts it but I do not think that is consistant with best practices. This is opinionated
What can be done is creating a service that will hold you model and share it through a controller to have access to it from both the view and the other services/models.
Session.js
function Session(){
var
self = this,
_islogged=false,
_name = '';
// functions
this.isLogged = function() {
return self.islogged;
};
this.setLoggedIn = function() {
self.islogged = true;
};
this.setLoggedOut = function() {
self.islogged = false; };
}
// GetUsername, setUsername ... Whatever you need
}
angular
.module('app')
.service('Session', Session);
rootController.js
function rootController(Session){
// share the Session Service with the $scope
// this.session is like $scope.session when using the controllerAS syntax.
this.session = Session;
}
angular
.module('app')
.controller('rootController', rootController);
I would suggest you take a look at these articles:
Techniques for Authentification in AngularJs Applications
Comprehensive 10 000 words tutorial in angular
Diving into controllerAs syntax
Your best to use a Service to share data as you mention. In your approach you've used a Controller in a way that its not really intended.
You can call your controller from your HTML by using ng-controller so something like the following should work. This would be useful for a Login view for example or a logout directive.
<div ng-controller="userCtrl">
<div id="nav-right-side" class="navbar-right" ng-switch on="isLogged()">
<div ng-switch-when="false">
<login-button></login-button>
</div>
<div ng-switch-when="true">
<loggedin-button></loggedin-button>
</div>
</div>
</div>
In order to have your session available globally for use elsewhere you can use a service which you can initialise from your app. The session data can be added to $rootScope which you can then reference from any view or controller.
Service
angular.module('app').service('session', function($rootScope) {
$rootScope.sessionData.loggedIn = true
// extra logic etc..
});
Main App
angular.run(session)
angular.module('app').run(function(session) {});
Then to reference the variable from your view
<div id="nav-right-side" class="navbar-right" ng-switch on="sessionData.isLoggedIn">
Note: its good practice to use an object with scope variables to help avoid issues with inheritance.

AngularJS:AppLevel controller possible?

I have a controller that is the controller for my page but i was wondering if its possible to have a AppLevel controller i.e. something that is accessible from every page... so each page would actually have more than 1 controller assigned.
I know i can probably do this with a service and inject the service but I was hoping for some kind of applevel controller that can be assigned.
If this possible, how would i communicate between the 2? I presume using dependency injection and just pass the applevel controller to my main page?
Anyone have an idea about this?
thanks
AngularJS leverages JavaScript prototypical inheritance in order for scopes to access properties on the parent scope. You can define the controllers nested in the HTML and access the parent from the child. However I would strongly urge you not to rely on this fact for your 'AppCtrl'. In some cases the scope you are working on will be isolated and will not be a part of the inheritance hierarchy that has access to the AppCtrl's scope.
I would suggest creating a service for this, or you could use pub/sub with $rootScope.$on and $rootScope.$broadcast.
To show the service example I'll use the words shellCtrl and shell service instead of app to make the example a little clearer.
The 'shell' service's job is to allow any other controller, directive or service in your app to interact with the shellController, and therefore the host view container.
<div ng-app="myApp" ng-controller="ShellCtrl">
<div ng-controller="SomeOtherCtrl"></div>
</div>
// parent controller defined on the same element as ng-app
function ShellCtrl($scope, shell) {
// I've just made the shell accessible to the $scope of shellctrl, but you can do
// this in various ways.
$scope.shell = shell;
}
// any other controller
function SomeOtherCtrl($scope, shell) {
shell.setTitle('Some title');
}
// basic example of the shell service
angular.module('myApp').factory('shell', function () {
return {
title = 'No title set',
setTitle = function (title) {
this.title = title;
}
}
});
Now you can set properties on the parent controller in a detached fashion without relying on the scope hierarchy.
When you have a child controller in Angular it inherits from the parent scope. So if you have one top level controller that contains functions that some descendant controller doesn't have then the top level function (or scope object) will be referenced. If one of the child controllers defines a local version of the function (or property on the scope) then it will no longer inherit from it's parent controller.
The Fiddle: http://jsfiddle.net/Y9yEQ/
The HTML
<div ng-app="myApp" ng-controller="TopLevelCtrl">
<button ng-click="testing()">Yo top level!</button>
<button ng-click="testing2()">Yo top level 2!</button>
<div ng-controller="ChildCtrl">
<button ng-click="testing()">Yo child!</button>
<button ng-click="testing2()">Yo child2!</button>
</div>
</div>
The JS
angular.module("myApp",[]).controller("TopLevelCtrl", function($scope){
$scope.testing = function() {
alert("just testing");
}
$scope.testing2 = function() {
alert("just testing parent");
}
}).controller("ChildCtrl", function($scope){
$scope.testing2 = function() {
alert("just testing child");
}
})
If you need to share some data between multiple controllers (since controller instances may be created or destroyed to support views as they are added/removed) you'll want to use a service. If you have a strict structure for your controllers you can $emit to bubble an event up scopes or $broadcast to send events down through scopes.

Resources