What is wrong with angular component "bindings" parameter? - angularjs

I have an angular component that I want to include in my page.
everything works if I comment/remove bindings parameter in this component... Why?
(function () {
angular.module('app').component('detailsComponent1', {
templateUrl: '/RealSuiteApps/RealForm/-1/Details/Component1',
restrict: 'E',
bindings: {
value: "Component1"
},
controllerAs: 'cm',
controller: function () {
this.message = 'Hello from component1';
}
});
})();
If I want to use binding parameter, I get an error:
that links to: https://docs.angularjs.org/error/$compile/iscp?p0=detailsComponent1&p1=value&p2=Component1&p3=controller%20bindings%20definition
What does it mean ??
Thanks.

Well, The error is coming because of Invalid Isolate scope
When declaring isolate scope the scope definition object must be in specific format which starts with mode character (#&=<), after which comes an optional ?, and it ends with an optional local name.
Please find the reference link here: https://docs.angularjs.org/error/$compile/iscp
Plus, you have some discarded directive property inside component.
restrict (restricted to elements for component)
Please find documentation for component here: https://docs.angularjs.org/guide/component
Hope it helps you!
Cheers

Related

Angular: Evaluate Expression Passed into Component Attribute

How can I pass in the value of an Angular expression to a component's attribute? I'm getting the value back from an API.
app.controller.js
$http.get(apiUrl).then(function(resp) {
$scope.name = resp.data.name;
})
...
person.component.js
export const Person = {
templateUrl: 'person.html',
bindings: {
firstName: '#'
},
controller: function() {
console.log(this.firstName);
}
}
app.html
...
<person first-name="name">
For some reason it's not evaluating name and it's logging undefined in the console.
Is there a way around this so it logs Tom inside the controller?
Any help is appreciated. Thanks in advance!
I've setup a jsFiddle here
& is for expressions, and # is for interpolated strings, so try using
firstName: '&'
and then this.firstName() should evaluate the expression passed in.
Also, firstName is not guaranteed to have been initialized until $onInit, so if you do
bindings: {
firstName: '&'
},
controller: function() {
this.$onInit = function() {
console.log(this.firstName());
}
}
you should get your expected result.
For reference: https://docs.angularjs.org/guide/component
$onInit() - Called on each controller after all the controllers on an element have been constructed and had their bindings initialized
Edit:
After the extra information you provided, you should probably use a one-way binding (<) instead for this case, because it appears you are just passing in a single value (instead of an expression), and then you can detect changes in $onChanges. I forked your jsfiddle to show a potential solution: http://jsfiddle.net/35xzeo94/.

AngularJS ─ can't get scope by link from the directive controller

I have an example app with very simple structure (you may see it here: http://plnkr.co/edit/5VAqUQsqKFGoteahacR2?p=preview):
The file index.html includes template (app/templates/home.html), which, in turn, includes the directive's template:
<div class="included" ng-include="'app/templates/outer-directive-2.html'"></div>
It includes the next directive:
<p>This is the included file <b>app/templates/outer-directive-2.html</b></p>
<div inner2="context"></div>
The value of the param inner2 is the key for the $scope.contents object which is defined in the contentsCtrl controller:
app.controller('contentsCtrl', function($scope,$rootScope){
$scope.contents = {
context: "Context for investigations here."
}
});
This key is needed to extract the object field in the directive's script (app/scripts/directives/defaultDirective.js):
app.directive('inner2', function(){
return{
restrict: 'A',
replace: true,
scope: {
inner2: '#',
contents: '&'
},
templateUrl: 'app/templates/inner-directive-2.html',
controller: function($scope){
$scope.getStuff = function(){
console.log('$scope.inner2, $scope.contents', {
// returns the key "context"
'$scope.inner2':$scope.inner2,
// returns function (???)
'$scope.contents':$scope.contents,
// returns "undefined"
'$scope.context':$scope.contents[$scope.inner2]
});
}
}
};
});
The content of that last folded directive (app/templates/inner-directive-2.html) is very simple:
<div class="included" title="{{inner2}}">
<p>Hello, I am the inner directive 2</p>
<span class="pseudolink" ng-click="getStuff()">Click me</span> and check console message!
</div>
So the idea is to get the $scope.contents[object_key] by calling getStuff().
But it can't see $scope.contents. I thought that it may be done by binding the isolated scope param (see above) contents to the outer scope:
scope: {
....
contents: '&'
},
...but it doesn't return the scope object, it returns function instead. Probably something is wrong here.
The questions are:
1. Why function and where it comes from?
2. May I get $scope.contents by some way and how?
http://plnkr.co/edit/5VAqUQsqKFGoteahacR2?p=preview
According to the angular`s docs:
& or &attr - provides a way to execute an expression in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. Given and the isolate scope definition scope: { localFn:'&myAttr' }, the isolate scope property localFn will point to a function wrapper for the count = count + value expression. Often it's desirable to pass data from the isolated scope via an expression to the parent scope. This can be done by passing a map of local variable names and values into the expression wrapper fn. For example, if the expression is increment(amount) then we can specify the amount value by calling the localFn as localFn({amount: 22})
so scope.contents in your directive will be a wrapper function.
You should either pass your contents object into directive scope or modify doStuff to execute it in the context of the parent scope.
Like in the doc (plese look at the example with increment(amount)) your doStuff should be in the parent scope. And from the directive you could pass locale variables
You are already getting scope contents. It's just that you didn't defined the default route to home rather it was set 404. Please check the updated plunker - http://plnkr.co/edit/BlXBqK?p=preview
Let me know if you are looking for something else.
app.config(function($stateProvider, $urlRouterProvider){
$urlRouterProvider.otherwise("/home");
$stateProvider
.state('home',{
url: "/home",
templateUrl: 'app/templates/home.html'
})
.state('error',{
url: "/404",
templateUrl: "app/templates/404.html"
});
});

Passing scope variables to an AngularJS controller using fat arrows

I'm updating an AngularJS application to use fat arrow syntax for anonymous functions. I know that I need to use version 1.5, but some things still don't work. For example, here I have a custom directive that passes the string 'hello' to its controller, which then outputs the string as an alert:
<div data-my-directive="hello">Text that will be replaced by my-template</div>
angular
.module('myModule')
.directive('myDirective', () => (
{
restrict: 'A',
templateUrl: 'my-template',
controller: 'myController',
controllerAs: 'myCtrl',
bindToController: true,
scope: {myVariable : '#myDirective'}
}))
.controller('myController', () => {
alert('the scope variable is: ' + this.myVariable );
}
But this alerts 'the scope variable is: undefined'.
If I change the controller definition to use ES5 syntax, then it alerts 'the scope variable is: hello', as expected.
.controller('myController', function() {
alert('the scope variable is: ' + this.myVariable);
}
I guess that this is something to do with the binding of this.
Is there a way to use the fat arrow notation when passing scope variables as above?
In this case, you have to use function instead of ()=>.
If you do :
.controller('myController', () => {
console.log(this); // -> window
})
If you use arrow function here, this === window. A controller need a real scope to work properly. I am pretty sure you doesn't want window as common object for all controllers in your app :)
Arrow function is really useful with class and callback but should not be use every time.
Angular calls the controller function like this: fn.apply(self, args);
where self (which becomes this in the invoked function) is an object that has the required fields - i.e. myVariable in the example.
Arrow functions ignore the first of apply's arguments. So as #Yoann says, we must use function() {...} rather than () => {...}.

Optional parameter on Angular Directive

I created a directive on Angular that receives 5 parameters and one of them is an optional array. The way I'm trying to deal with it is like follows:
app.directive('fooDirective', function() {
return {
restrict: 'AE',
scope: {
param1: '=',
param2: '=' // optional array
},
template: //...
"<div ng-class='defineClass()'> Message </div>"
//...
controller: function($scope) {
if (typeof $scope.param2 === 'undefined')
$scope.param2 = [];
console.log($scope.param2);
$scope.defineClass = function() {
if ($scope.param2.length == 0) return 'gray-text';
return 'red-text';
};
// ......
}
}
});
At some point in my code I check for the .length of param2 and if it is undefined it throws a lot of errors. What is driving me nuts is that the console.log() you can see there outputs a [], indicating that the .length of my param should be 0 and the errors on the console are shown after the output of the console.log().
So I guess I am missing something about either the way Angular binds the scopes or the flow that the directive is constructed. I have tried verifing my param on both link and compile phases and got the same problem.
So, what am I missing here? Thanks in advance.
From the angular documentation (see the section for scope bi-directional binding):
= or =attr - set up bi-directional binding between a local scope property and the parent scope property of name defined via the value of the attr attribute....
If the parent scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You can avoid this behavior using =? or =?attr in order to flag the property as optional.
So the solution to make the parameter optional is to change the binding to be an optional two-way binding with the additional ?.
...
scope: {
param1: '=',
param2: '=?' // notice the ? makes this parameter optional.
},
...

How to refactor directive and change functionality by checking parent scopes?

I have a form with a ton of duplicate functionality in 2 different Controllers, there are slight differences and some major ones in both.
The form sits at the top of a products view controller, but also inside of the products modal controller.
Test plunker: http://plnkr.co/edit/EIW6xoBzQpD26Wwqwwap?p=preview
^ how would you change the string in the console.log and the color of the button based on parent scope?
At first I was going to create a new Controller just for the form, but also the HTML was being duplicated, so decided to put that into a Directive, and just add the Controller code there.
My question now is this: How would I determine which parent scope the form-directive is currently being viewed in? Because depending on the parent scope the functions/methods behave differently.
So far I've come up with this:
.directive('productForm', function() {
return {
templateUrl: "views/products/productForm.html",
restrict: "E",
controller: function($scope) {
console.log('controller for productForm');
console.log($scope);
console.log($scope.$parent);
/*
If parent scope is the page, then this...
If parent scope is the modal then this instead...
*/
}
}
});
However it's giving me back $parent id's that look like 002 or 00p. Not very easy to put in if / else statements based on that information.
Have you guys run into this issue before?
You can define 'saveThis' in your controller and pass it to directive using '&'
scope: {
user: '=',
saveThis : '&'
},
please see demo here http://plnkr.co/edit/sOY8XZtEXLORLmelWssS?p=preview
That gives you more flexibility, in future if you want to use saveThis in another controller you can define it inside controller instead adding additional if statement to directive.
You could add two way binding variables in the directive scope, this allows you to specify which Ctrl variable gets bound to which directive variable
<my-directive shared="scopeVariable">
this way you achieve two way binding of the scopeVariable with the shared directive variable
you can learn more here
I advice against this practice and suggest you to isolate common logics and behaviours in services or factories rather than in directives
This is an example of a directive that has isolated scope and shares the 'title' variable with the outer scope.
You could declare this directive this way:
now inside the directive you can discriminate the location where the directive is defined; just replace the title variable with a location variable and chose better names.
.directive('myPane', function() {
return {
restrict: 'E',
scope: {
title: '#'
},
link: function(scope, element, attrs, tabsCtrl) {
},
templateUrl: 'my-pane.html'
};
});

Resources