Angular directive: passing scope objects - javascript

I got a directive which has a model passed by an attribute:
use strict;
angular.module('ebs-front')
.directive('ebsIa', function() {
return{
restrict: 'A'.
scope: {
opened: '=ebsIaOpened',
model: '=ebsIaModel',
cb: '&ebsIaCb'
},
controller: function($scope, $uibModal){
console.log('check');
$scope.text = { text: 'test'};
$scope.$watch('opened', function(newValue) {
if(newValue === true){
var modalInstance = $uibModal.open({
controller: 'ImpactAnalyseController',
templateUrl: 'common/directive/ebs-ia-template.html'
});
}
});
}
}
});
In this directive, I need to do some operations and then open a modal window. So for so good, but the thing is, I want the $scope.model to be accessible in ImpactAnalysisController as well.
My assumption was that $scope.test and $scope.model will be available in ImpactAnalysisController automatically, but apparently a isolated scope is created which is only valid for the controller: function part.
What would be a good way to pass the model variable of the scope to the ImpactAnalysisController?! And why isn't it default behaviour in angular?
If I define my directive like below, then the removeFromFilters (in this case) IS available in the directive, so I'm kinda puzzled. Any help would be appreciated...
use strict;
angular.module('ebs-front')
.directive('ebsIa', function() {
return{
restrict: 'A'.
scope: {
opened: '=ebsIaOpened',
model: '=ebsIaModel',
cb: '&ebsIaCb'
},
controller: 'ImpactAnalysisController'
};
)};

There are several ways to share data between controllers in Angular. A couple that come to mind:
1- Use a $rootScope.broadcast('keyName', value) and listen for the value with $scope.on('keyName', function(){...} Use with care, not the best approach most of the time
2- Keep the data not in the controller but in a Service or Factory, and inject that into your controllers (preferable)

What would be a good way to pass the model variable of the scope to the ImpactAnalysisController?!
Depends on what the controller has access to and intends to do with it.
And why isn't it default behaviour in angular?
You're asking the wrong question. You chose an Isolate Scope. Why did you choose an Isolate Scope, if you wanted to inherit properties from its parent?
What may solve your problem:
If you're passing a pure model and expect to have some IO where the user is potentially altering the model I recommend reading and implementing: NgModelController
It will make the model and mechanisms to interact with it available to your directive(s) via an injectable Controller, independent of the type of scope you choose. All you have to do is require 'ngModel' according to $compile documentation.

Fixed with uibmodal's resolve functionality:
var modalInstance = $uibModal.open({
animation: $scope.animationsEnabled,
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
})
Item is passed from the parent scope to the ModalInstanceCtrl and becomes available in the controller as variable. Exactly was I was looking for!

Related

Updating a directive value from a controller in AngularJS

I'm having a bit of trouble understanding scopes and the relation between scopes in a directive and scope in a controller..
I have a directive displaying a navbar like this, in the navbar I want to display a name, that i stored using localStorage
(function (module) {
var navbar = function () {
return {
restrict: "AE",
controller: function ($scope, localStorage) {
$scope.name = localStorage.get("name");
},
templateUrl: "/app/NSviewer/templates/nav.html"
};
};
module.directive("navbar", navbar);
}(angular.module("anbud")));
Now when this page is loaded for the first time the localStorage haven't set the name value. So the navbar gets name = null. Then the controller does:
localStorage.add("name", name);
and the name is set, if I refresh the page the navbar is loaded again, this time the name is stored in localstorage and it is displayed correctly.
So I want to do something like $scope.name = 'John Smith' in the controller and then have my directive / navbar update.
Storing a value to be shared between a controller and a directive in local storage is overkill. Typically you do the following:
(function() {
'use strict';
angular
.module('app')
.directive('dashboard', dashboard);
function dashboard() {
var directive = {
restrict: 'E',
controller: 'DashboardController',
controllerAs: 'vm',
bindToController: {
msg: '#'
},
scope: {},
templateUrl: '/templates/dashboard/dashboard.html'
};
return directive;
}
angular
.module('app')
.controller('DashboardController', DashboardController);
DashboardController.$inject = [];
function DashboardController(){
var vm = this;
vm.msg = 'Hello World'
}
})();
Some things to note:
The bindToController can accept an object. This is something that could only accept a boolean pre angular 1.4. Now it can act just like the scope property and attach stuff to be used by your controller to pass data.
The use of # means that it's a 1 way data bound string. = sets up a two-way bound relationship and there's also the & property. See this post for an overview of what they all mean
Another difference between what I'm doing is that I'm using var vm = this as opposed to injecting $scope This is quite a popular approach nowadays and means you don't get riddled with scope soup. But you can think of it as a way to do the same thing essentially (that is binding stuff but it cannot listen for events so please remember that)
Good luck

AngularJS: Communication between controllers in different views

I have different views each created by a different controller. At a particular time only one of the views is visible.
I want to switch from one view to another view through a function of the controller of the first view and after that I want to call a method of the second view controller.
My problem is how should I call this method in an angular way?
I know the possiblity using $broadcast and $on but that smells a little bit.
The other choice ist to find the scope in the dom and calling the method via scope. But that is even more ugly.
What is the best solution?
You can use services to communicate between controllers. While you could create a generic shared service to have a central point to subscribe to and broadcast events, services are easier to maintain over time.
You can use Angular Routing
Check out the documentation. This is an excerpt from the documentation. You can make links like
Link
For the first route and so on.
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
Okay it is done and simpler as expected.
The idea is to use a service used in both views (controllers), that contains a 'execution boolean'.
The first view set the boolean to true, the second set a watch on this boolean and therefore is called and can call the desired method.
In the service:
trigger: function(name) { model[name] = true; },
setTriggerWatch: function(scope, name, callback) {
scope.$watch(function value() {
return model[name];
}, function listener(newValue, oldValue) {
if (newValue) {
callback();
}
});
},
In the destination controller:
sessionData.setTriggerWatch($scope, 'createSession', function callCreateSession() {
_createSession();
});
In the source controller:
sessionData.trigger('createSession');

Isolate scope doesn't work the way I think it should

I have a fundamental misconception about isolate scopes and I can't figure out what it is.
I have a controller and directive:
.controller('MyCtrl', [
'$scope',
function($scope) {
$scope.zed = "ZZZZZ";
$scope.zz = function() {
return "yep";
};
}])
.directive("myDirective", function() {
return {
restrict: "AE",
controller: "MyCtrl",
templateUrl: 'a/path/to/my_template.html',
scope: {
z: '#'
}
};
and the template:
<div>***{{z}} {{zz()}} ^^^ {{zed}} </div>
and the use of the directive:
<div my-directive z="yabba"/>
When I run this app, the value of 'yabba' is displayed for z. I'm good with that. The isolate scope makes this possible. However, the template reaches right into the $scope and is able to run a function and grab a scalar (zed and zz). I don't want that. The controller needs access to $scope since the model upon which I'll eventually operate has to be assembled from $scope data.
My desire is to limit the directive to as little information as possible.
I would like the $scope to be available to the controller but not the directive. I want the directive to have to gets its data from the controller exclusively. I tried to add z and zed to the isolate scope but this did nothing helpful.
How can I do this? Or, is my approach simply foreign to Angular and bad? If so, that's cool, please explain to me the better approach. And, please, use small words.
You are stepping on your toes by including your controller in the directive. Just delete this line from your directive and it will behave as you guessed: controller: "MyCtrl",
Here is a plunker to mess around with. You can see that your isolated scope is working as it should with the line commented out. Just un-comment that line again and see how you now have access to the controller scope.
.directive("myDirective", function() {
return {
restrict: "AE",
// controller: "MyCtrl", REMOVE THIS LINE!!!
templateUrl: 'a/path/to/my_template.html',
scope: {
z: '#'
}
};
Plunker playground with your code
By the way, there is nothing foreign in your code. Isolated scope is a great feature to use with custom directives. There are many ways to work with Angularjs 1.+. This may not be the case when Angularjs 2.0 is released. In your example, the controller is not needed within the directive. I rarely include controllers, and I most always isolate scope. Good luck! Hope this helps.

ngBind equivalent of NgModelController (or best practice)

http://plnkr.co/edit/C4mFd5MOLBD2wfm8bMhJ?p=preview
Let's take a simple example and say you want to display the value of a cookie regardless of what it is, but this could be a customer name or whatever you want. There seem to be so many options available: directives, services, directives with services, controllers - and no matter how many docs I review or blog posts I read, I still have some fundamental questions about the appropriate way to access data and then update the scope accordingly.
What's clouding my thought right now is the fact that there doesn't seem to be the equivalent of NgModelController for non ngModel capable DOM elements like span or div or anything besides user input. Basically, seeing how ngModelCtrl is utilized in the link function of a directive seems to make a lot of sense, it doesn't allow you to drown in scope soup and it nicely organizes your thoughts, but how do we achieve this decoupling with ngBind elements?
I think the answer is just 'use services', but perhaps maybe not in all cases is the thing that's gnawing at me. Suppose you want to display a very specific cookie (or a customer name) and you don't know where you want to display it, you could continually inject your custom CookieService where ever you go, but what about a specific directive that cleans things up: <specific-cookie></specific-cookie> Would we just inject our CookieService into that directive, or just access it via $cookies like we've done elsewhere.
Does the answer simply lie in whether or not you'll be accessing more than one cookie in the future? That is, if you only need one <specific-cookie></specific-cookie>, then just use $cookies in you're directive and move on with your life, or it is always appropriate to abstract away this type of call into a service, or am I just being super pedantic about understanding this.
Directive
angular-myapp.js
var app = angular.module('myApp', ['ngCookies']);
app.directive('cookie', ['$cookies', function($cookies) {
return {
scope: true,
controller: function($scope, $element, $attrs) {
$scope.cookie = $cookies[$attrs.cookie];
}
}
}]);
index.html
<div cookie="__utma">Cookie: {{cookie}}</div>
Controller
angular-myapp.js
app.controller('CookieCtrl', function($attrs, $cookies) {
this.value = $cookies[$attrs['getcookie']];
});
index.html
<a ng-controller="CookieCtrl as cookie" getCookie="__utma" href="/{{cookie.value}}">{{cookie.value}}</a>
Service
angular-myapp.js
app.controller('SomeCtrl', function($scope, CookieService) {
$scope.cookie = CookieService.getCookie('__utma');
});
app.service('CookieService', function($cookies) {
var getCookie = function(cookie) {
return $cookies[cookie];
};
return ({ getCookie: getCookie });
});
index.html
<div ng-controller="SomeCtrl">Cookie: {{cookie}}</div>
Directive with service
angular-myapp.js
app.directive('specificCookie', function(CookieService) {
return {
scope: true,
template: 'Cookie: <span ng-bind="cookie"></span>',
controller: function($scope, $element, $attrs) {
$scope.cookie = CookieService.getCookie('__utma');
}
}
});
index.html
<specific-cookie></specific-cookie>
Unless I'm misunderstanding some of your scenarios, the simplest (and proper) way to do this is to create a reusable directive that displays a cookie based on a name passed to it via its attribute.
app.directive('cookie', ['$cookies', function($cookies) {
return {
scope: {},
template: "<span>{{cookie}}</span>",
restrict: "E",
link: function(scope, element, attrs) {
attrs.$observe("name", function(newVal){
scope.cookie = $cookies[newVal];
});
}
};
}]);
The usage would be trivial (and page controller-independent):
<cookie name="__utma"></cookie>
<input ng-model="cookieName" type="text">
<cookie name="{{cookieName}}"></cookie>
the resulting DOM would be:
<span class="ng-binding">137862001.838693016.141754...</span>
<span class="ng-binding">GA1.2.838693016.1417544553</span>

How to inject service depending on directive attribute in angular?

I have the following directive
directiveModule.directive('typeahead', function() {
return {
restrict: 'E',
transclude: true,
scope: 'isolate',
templateUrl: 'assets/partials/common/typeahead.html' ,
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
console.log(eval($attrs.service + ".query();"));
$scope.search = function(){ console.log("Searching for:" + $scope.selectedValue) };
$scope.selectedValue = "";
$scope.providedItems = [];
}],
};
});
With the following template:
<div>
<input ng-change="search()" ng-model="selectedValue" ng-click="log()" autocomplete="off" type="text"/>
<ul class=".typeahead">
<li ng-repeat="item in providedItems">{{eval(item + "." + descriptor)}}</li>
</ul>
and the following call inside my view:
I would like to let the service attribute be evaluated at runtime when injecting to the controller in the directive. So that where it now says
controller: ["$scope","$element","$attrs","$transclude", "Event",
function ($scope,$element,$attrs,$transclude, Event){
It would say something similar to:
controller: ["$scope","$element","$attrs","$transclude", eval($attrs.service),
function ($scope,$element,$attrs,$transclude, eval($attrs.service)){
However I can't access the $attrs from the scope inside the directiveModule call.
If there is any way to access a service declared in the view that would suffice. Please help!
One solution for this, would be creating and binding the controller yourself. All you need is to inject both $injector (in order to resolve the service dynamically) and $controller (in order to resolve the controller dynamically). And in your linking function you create the controller yourself:
link: function(scope, elm, attr) {
$controller(function YourController($scope, dynamnicService) {
dynamicService.query();
}, {
$scope: scope,
dynamicService: $injector.get($attr.service)
}
)
}
There is one thing important here. I'm considering, in this example, that your service is a string in the element attribute. If it's a string inside the scope, referred by the attribute, then you have to change the approach. You should $attr.observe the attribute, and on change, you should grab the service $injector.get(...) and pass it to the controller. You ould either create a this.setService method on the controller itself, or $scope.setService method, your call. I'd rather the controller, as this is related to accessing a service. Using this second approach, you don't need to create the controller by hand, you can simple expose this method and set the data from outside.
One more info, you should NEVER TOUCH THE DOM FROM YOUR CONTROLLER. So passing $element, $attr and $transculde to the controller is probably a bad idea, no matter what.
Take a look at the docs about $controller, $injector and directive.

Categories

Resources