Controller without $scope - angularjs

I just come across some code where no $scope was there in the controller.
Here is the code
<div ng-app="myApp" ng-controller="ctrlCarLists as cars">
<button ng-click="cars.showCars()">
Cars
</button>
<button ng-click="alert(cars.data)">
Test
</button>
</div>
var app = angular.module('myApp', []);
app.controller("ctrlCarLists", function () {
this.data = 'hello';
this.showCars = function () {
alert("Ford, Toyata, Mercedes");
};
});
The above code is running, but this.data is not accessible...why? showCars() is accessible when the button is clicked. Why isn't this.data is not accessible?
What should be called when we declare any variable or function inside controller with the this keyword? Will it behave like a static property or function of a class?
jsfiddle link https://jsfiddle.net/tridip/z5wkzc0g/

ng-controller="ctrlCarLists as cars"
"cars" is referring to your controller, and "this" is refering to your controller as well.
ng-click="cars.showCars()"
When this function is triggered, it will refer to "showCars()" function in your controller.

As Yury Tarabanko has explained in his comment, alert function is not found, reason is:
https://docs.angularjs.org/guide/expression#context
Angular does not use JavaScript's eval() to evaluate expressions.
Instead Angular's $parse service processes these expressions.
Angular expressions do not have access to global variables like
window, document or location. This restriction is intentional. It
prevents accidental access to the global state – a common source of
subtle bugs.
Instead use services like $window and $location in functions called
from expressions. Such services provide mockable access to globals.
Source:
Why is ng-click not working?

ng-click requires an expression (see docs).
alert(cars.data)
is not an expression that angularJS can compile (it doesn't know what alert refers to) thus it should be replaced with
ng-click="cars.data()"
because cars.data() is an expression it can compile.
Fiddle
Or, alternatively
Fiddle
where we could have this:
this.alert = function (text) {
alert(text);
}

Related

angular 1.5: watch doesn't call when i toggle the ui

I my template i have:
<md-checkbox ng-model="$ctrl.isAdOps" aria-label="isAdOps">
isAdOps {{ $ctrl.isAdOps }}
</md-checkbox>
In my component:
(function (app) {
app.component('homeComponent', {
templateUrl: 'partials/home-partial.html',
bindings: {
isAdOps: '='
},
controller: ['$scope', '$state', function ($scope, $state) {
var self = this;
$scope.$watch(
self.isAdOps,
function (isAdOps) {
$scope.$broadcast('isAdOpsToggled', isAdOps);
}
);
}]
});
})(myApp);
why doesn't the watch called when i toggle the md-checkbox?
Angular $watch first parameter should of type string/function
string: Evaluated as expression
function(scope): called with current scope as a parameter.
replace self.isAdOps with 'isAdOps' in $watch or alternativly use a function syntax with $scope.
You are better off using the ng-change directive:
<md-checkbox ng-model="$ctrl.isAdOps"
ng-change="$ctrl.isAdOpsChanged($ctrl.isAdOps)"
aria-label="isAdOps>
isAdOps {{ $ctrl.isAdOps }}
</md-checkbox>
It avoids using $scope and adding a watcher.
For more information, see Writing Components without Watchers.
See also, AngularJS Developer Guide - Component-based application architecture.
Components should follow a few simple conventions:
Inputs should be using < and # bindings. The < symbol denotes one-way bindings which are available since 1.5. The difference to = is that the bound properties in the component scope are not watched, which means if you assign a new value to the property in the component scope, it will not update the parent scope. Note however, that both parent and component scope reference the same object, so if you are changing object properties or array elements in the component, the parent will still reflect that change. The general rule should therefore be to never change an object or array property in the component scope. # bindings can be used when the input is a string, especially when the value of the binding doesn't change.
Outputs are realized with & bindings, which function as callbacks to component events.
This will make the migration to Angular 2+ easier.

How to call a custom directive's action in AngularJS?

In AngularJS you can make a button to call an action like this:
<div ng-controller="myController">
<button ng-click="onButtonClicked()">Click me</button>
</div>
So, I'm inserting a custom directive like this:
and in my-canvas.js directive file's link function I replace the tag with a KineticJS canvas. Then, User manipulate the canvas by dragging around Kinetic shapes and, finally, when User does with the shapes what he's required to do, I want the directive to call an action defined on myController. I'm thinking about something like this:
<div ng-controller="myController">
<my-canvas ng-success="onScenarioSuccess" />
</div>
but I can't figure out how the correct way to do it.
How can I make a directive to call it's action/event programmatically?
When you want your directive to expose an API for binding to behaviors you should use an isolate scope and use the & local scope property. This allows you to pass in a function that the directive can invoke. Here is a simple example:
.directive('testDirective', function () {
return {
restrict: 'E',
scope: {
action: '&'
},
template: '<button ng-click="action()">Test</button>'
};
});
And use it like:
<test-directive action="myControllerFunction()"></test-directive>
As per the documentation:
The & binding allows a directive to trigger evaluation of an
expression in the context of the original scope, at a specific time.
Any legal expression is allowed, including an expression which
contains a function call. Because of this, & bindings are ideal for
binding callback functions to directive behaviors.
There'are some more detail in the documentation.
If you want to expose a custom event like ng-success and want to call a function on the event.
You can either do what #Beyers has mentioned using isolated scope.
Or else look at the source code of ng-click, it just wraps the javascript event inside $scope.apply, using the $parse service to evaluate the expression passed to it. Something like this can be added in your link function
var fn = $parse(attr['ngSuccess']);
element.on('someEvent', function (event) {
var callback = function () {
fn(scope, {
$event: event
});
};
scope.$apply(callback);
});
The advantage of this mechanism is that isolated scope is not created.

How to initialize form as scope variable in controller

I have a simple angular form
<form name="myForm" ng-controller="myFormController"></form>
and need to call $setPristine() to myForm in myFormController. What is the best way to initialize this form as a $scope variable?
I tried $scope.myForm.$setPristine(); but it gave me:
Cannot read property '$setPristine' of undefined
Thanks in advance.
EDIT From the docs:
name (optional)
Name of the form. If specified, the form controller will be published into related scope, under this name.
That means you can access it in a controller, but how?
form directive does publish the name of the form to the scope. But if the form is nested inside the ng-controller element, then the form's scope variable is not yet available when the controller function runs.
As an illustration:
<div ng-controller="OuterCtrl">
<form name="form1">
<div ng-controller="InnerCtrl"></div>
</form>
</div>
The following would happen:
.controller("OuterCtrl", function($scope){
// $scope.form1.$setPristine(); // this will fail
})
.controller("InnerCtrl", function($scope){
$scope.form1.$setPristine(); // this will succeed
});
It is rarely needed to access the form when the controller function runs. Typically, it's done in response to some action, like a submit action. When that happens, the "OuterCtrl" will have $scope.form1:
.controller("OuterCtrl", function($scope){
$scope.submitForm = function(){
//... do something with form data
$scope.form1.$setPristine();
}
});
In that respect, $timeout would, in fact, work and would not cause race conditions. But you should re-examine why you need it the form when the controller function first runs.
Try this in your controller.
$timeout(function () {
// here you should be able to access $scope.myForm
})
I just wasted some time dealing with this. In my case, I had the form shown a bit later using a ng-if. Changing the form conditions to an ng-show resolved the non-initialization of the $scope.myform.

A controller scope variable passed from function invoked in a template does not change the original controller variable when the parameter is changed

I have a scope variable, $scope.test. If I pass this variable in a function that is invoked in a template as an argument and then I try to change it in the controller, it seems that the original $scope.test does not change. Only the local variable, foo changes. But isn't foo a reference to $scope.test?
var mod = angular.module('app', []);
mod.controller('myCtrl', function($scope) {
$scope.test = 'test';
$scope.doSomething = function(foo) {
foo = 'scope.test should change';
}
})
Here, I'm passing test to the doSomething function which is in the controller above.
<body ng-app='app'>
<div ng-controller='myCtrl'>
<button ng-click="doSomething(test)">testing</button>
<h1>{{test}}</h1>
</div>
</body>
Here's the plunker: http://plnkr.co/edit/JBsos0qVJP47WgfD9Fee?p=preview
This is indeed something that you might want to do within Angular.js. However, in your case, you are passing a primitive string, which is passed by value instead of by reference in JavaScript. You can pass objects and change the properties of the objects, but when you pass primitives, they are not mutable. This is also a reason why one of the best practices for Angular.js is that any bindable items have a ., which is automatically a property of an object.
http://docstore.mik.ua/orelly/webprog/jscript/ch11_02.htm

Tabs directive not exposing an api in my scope

So I'm trying the Tabs directive and having some problems.
the structure is something like:
//routes
$routeProvider..when('/course/:id', {
controller: 'CourseCtrl',
templateUrl: '/app/views/course.html'
});
//course.html
<div ng-controller="CourseTabsCtrl">
<tabset>
<tab>
<tab-heading>Title</tab-heading>
<div ng-include="'/view.html'"></div>
</tab>
....
</tabset>
</div>
Problem is i can't access the api to enable or disable tabs, select a tab, in none of the controllers CourseTabsCtrl or CourseCtrl.
Is this because the directive is working on an isolated scope? and if so, is there a way to get around that? How can i fix it?
Thanks
Looking at the source and the documentation, you should be able to pass in an expression to the <tab> directive, that dictates whether it is enabled or disabled.
app.controller('CourseTabsCtrl', function ($scope) {
$scope.expr = true;
});
<div ng-controller="CourseTabsCtrl">
<tabset>
<tab disabled="expr">
<tab-heading>Title</tab-heading>
<div ng-include="'/view.html'"></div>
</tab>
....
</tabset>
</div>
If however, this is not enough for you. You could 'hack' the tabset directive and use another controller than the one currently specified. Now, you would have to replicate the old behaviour of the default TabSetController, but you could add functionality on top of it to cater to your needs.
The best way (I've found) to do this is to decorate the directive itself.
Like so:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
dir.controller = 'CourseTabsController';
return $delegate;
});
});
You could build further on this and pass in the controller to the directive itself.
<tabset ctrl="someCustomCtrl"></tabset>
The config block would then look like this:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
dir.controller = function ($scope, $element, $attrs, $controller) {
return $controller($attrs.ctrl, {
$scope: $scope
});
};
return $delegate;
});
});
Note: If you go with the decorator way, you may have to do it in the config block for the angular-ui module. In that case, you will probably want to have a look here, to be able to configure third party modules without touching their core code.
A second gotcha to the passed-in controller way, is that you need to make use of $injector in order to get dependencies (apart from $scope, $element and $attrs) into the controller. This can either be done in the config block, or in the controller itself by adding $injector as a dependency, like so:
app.controller('CourseTabsController', function ($scope, $injector) {
var $timeout = $injector.get('$timeout');
// etc...
});
What the f* am I on about?
Given that I don't personally work with the AngularUI Bootstrap components, and the fact that there is no plunker/jsBin available I'm throwing out some tips n tricks on how to add custom behaviour to third party components, without polluting their core code.
To address the questions at the end of your post:
Is this because the directive is working on an isolated scope?
It very well might be, the idea of isolated scopes is to not pollute the outside world with their inner properties. As such, it's highly likely that the only live 'endpoint' connected to the <tab> directive API is the default AngularUI TabSetController.
... and if so, is there a way to get around that? How can i fix it?
You can either do what I've suggested and roll your own controller (bare in mind that you should duplicate the code from the TabSetController first), that way you should have full access to the endpoint of the <tab> directive. Or, work with the options that are available to the directive as of this writing and wait for some more functionality to be introduced.
I'll try to fire up a jsBin soon enough to further illustrate what I mean.
Edit: We can do the whole decorator dance and duplication of the old controller behaviour, without the need to pass in a new controller. This is how we would achieve that:
app.config(function ($provide) {
$provide.decorator('tabSetDirective', function ($delegate, $controller) {
// $delegate in a directive decorator returns an array. The first index is the directive itself.
var dir = $delegate[0];
var origController = dir.controller;
dir.controller = function ($scope) {
var ctrl = $controller(origController, { $scope: $scope });
$scope.someNewCustomFunction = function () {
console.log('I\'m a new function on the ' + origController);
};
$scope.someNewCustomFunction();
return ctrl;
};
return $delegate;
});
});
Here's a jsBin illustrating the last example: http://jsbin.com/hayulore/1/edit

Resources