angularJS 1.5 nested components - angularjs

I am trying to build a parent view (with a controller) that has a angularjs component. The parent view delivers the data to the component by two way binding (it is a result of an Web-Service, so the data is a JSON Object). A part of the data is again delivered from the component to another nested component.
Now when I change the data in the nested component by a textfield, following exception occurs:
angular.js:13424 Error: [$compile:nonassign] Expression 'undefined' in attribute 'attributDetailDto' used with directive 'catAuswertungsparameterBearbeitung' is non-assignable!
JS of Component
angular.module('catApp').component('catAuswertungsparameterBearbeitung', {
controller : CatAuswertungsparameterBearbeitungController,
templateUrl : 'resources/js/konfiguration/auswertungsparameter/catAuswertungsparameterBearbeitung.html',
bindings : {
attributDetailDto : '='
}
});
function CatAuswertungsparameterBearbeitungController($translate) {
var ctrl = this;
}
HTML of component
...
<cat-textfeld min=1 max=50 wert="$ctrl.attributDetailDto.bezeichnung"></cat-textfeld>
...
JS of nested component cat-textfeld
angular.module('catApp').component('catTextfeld', {
controller : MinMaxTextfeldController,
templateUrl : 'resources/js/fwk/catTextfeld.html',
bindings : {
wert : '=',
min : '#',
max : '#'
}
});
function MinMaxTextfeldController($translate) {
var ctrl = this;
HTML of nested component
<input type="text" class="textfeld" name="textfeld" ng-model="$ctrl.wert">
Do you have any ideas? Many thanks :)

Oh man, you couldn't help me because I totally failed in calling the first component.
This didn't work for sure:
<cat-auswertungsparameter-bearbeitung attributDetailDto="attributDetailDto"></cat-auswertungsparameter-bearbeitung>
Because angular also divides camel-case attributes:
<cat-auswertungsparameter-bearbeitung attribut-detail-dto="attributDetailDto"></cat-auswertungsparameter-bearbeitung>
sorry for sapping your time

When using bindings in Angular 1.5, you are able to declare the binding parameteres as optional, like this: wert: '=?'. This way, using the catAuswertungsparameterBearbeitung component will not force using all of its bindings attributes.

a small advice, start using a pattern like this
app.component("someComponent", {
bindings: {
something: '='
},
templateUrl: "app/templates/layer-list-component.html",
controllerAs: "model",
controller: function () {
var model = this;
}
});
and then
<input type="text" class="textfeld" name="textfeld" ng-model="model.wert">
to avoid confusion when using the $ symbol

Related

Prevent ng-if to print html template in angular js component

I have the following component, I need in few places in the code do not show that component but I don't want to do that outside the component because I will need to add ng-if in different places to check if the status is 1 then do not show the directive.
how can I do that in the component? maybe do I need a controller and then don't print the HTML?
I do not want to use ng-if because I am trying to reduce the use of watchers also
component :
.component('comp', {
template: '<span class="class"><another-component></nother-component></span>' +
'<span class="status {{$ctrl.textClass}}" ng-bind-html="$ctrl.text" ></span>',
bindings: {
textClass : '<',
text : '<'
status : '<'
}
});
You can inject $element in your component's controller, then remove element based on some condition (whatever meets your specifications).
e.g.
.component('someComponent', {
controller: someComponentController,
template: 'Hi'
})
someComponentController.$inject = ['$element'];
function someComponentController($element) {
const earthIsRounded = true;
if (earthIsRounded) {
$element.remove();
}
}
Some working fiddle you can play around with.

unable to get $scope form name on load

I'm trying the retrieve the form name inside my angularjs component while the form is being loaded as I wanted to set the form state to dirty based on some data validations that were resolved in to the component. I'm able to access the form name once the form is completely loaded say inside a submit, however i'm unable to do that on the load how can I do that. I'm using ui.router hence the controller name is being set based on the state.
<form class="form-horizontal" name="detail.myForm">
<button ng-click="detail.submit">
</form>
app.component('myDetail', {
bindings: {
alldetails: '<'
},
templateUrl: '/app/detail.html',
controllerAs: 'detail',
controller: function ($state, $transitions, $scope) {
var detail=this;
/*validateData in the alldetails here */
$scope.detail.myForm.$setDirty(); // issue here saying undefined
detail.submit = () =>{
$scope.detail.myForm.$setPristine() //works without any issue
}
}
This happens since the DOM isn't ready on your controller's construction. You have to use the $onInit callback instead. From AngularJS docs:
$onInit() - Called on each controller after all the controllers on an element have been constructed and had their bindings initialized (and before the pre & post linking functions for the directives on this element). This is a good place to put initialization code for your controller.
Also, it'd be better to inject the ngFormController by using the require object instead of assigning it to your model.
Here's a fiddle with a working example. The relevant code is:
.component('myDetail', {
template: '<h1>Details Component</h1>',
controllerAs: 'detail',
// By requiring the form controller, angular will
// create a 'formCtrl' property on your controller with the
// ngFormController instance of the parent form.
require: {
formCtrl: '^form'
},
controller: function() {
// We can't just acces the formController here, couse it will be
// undefined, since the dom isn't ready yet. So we have to use the
// $onInit callback that will be executed by angularjs.
this.$onInit = function() {
/*validateData in the alldetails here */
this.formCtrl.$setDirty();
}
}
});

AngularJS 1.5 - How to Set Two Way Bindings on Component

Directives in Angular 1.X are set to have two way binding by default. Components have isolated scopes by default. I have a view that looks like:
<div class="my-view">
{{controllerVariable}}
</div>
If I have the above set up as a directive, the controllerVariable loads correctly in the following situation:
<div ng-controller="myController">
<my-view></my-view>
</div>
But if I have it set up as a component using the following:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: '='
});
then the variable value isn't displayed. I have tried adding $ctrl to the variable:
<div class="my-view">
{{$ctrl.controllerVariable}}
</div>
but this doesn't display the value either.
What am I missing here?
You need to pass the value from the directive into the component:
<my-view passed-var='ctrl.passedVar'></my-view>
and in the component:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: {
passedVar: '='
},
controller: function () {
var vm = this;
console.log(vm.passedVar);
}
});
then you will be able to access in the component as in the example
There are a few other ways to do it, such as using a service to handle the information or using require which would give your component access to the controller of the directive. You can find the above method and others here: https://docs.angularjs.org/guide/component.
I had to explicitly state the variable I wanted to bind:
myApp.component('myView', {
templateUrl: '/path/to/view',
bindings: {
controllerVariable: '#'
}
});
Also, since controllerVariable is a string, I had to use the # sign binding.

Cannot bind to rootscope in Angular 1.5 component

I'm in the process of eliminating the "scope soup" architecture of a legacy Angular 1.5 app following this guide: http://teropa.info/blog/2015/10/18/refactoring-angular-apps-to-components.html#replace-external-reference-with-bound-input
I'm trying to remove the reference to $rootscope.taskui, so I tried to add a binding to the component. Unfortunately, taskui is now undefined. The "component" is an Angular 1.5 component (which is just a normal directive under the hood). Am I doing something wrong?
If you replace "this.taskui" with "$rootscope.taskui" (correctly injected), method prints the taskui object just fine.
export default {
bindings: {
taskui: '='
},
controller,
templateUrl: "component.html"
};
Here's the controller code:
class Controller {
constructor() {
this.method = () => console.log(this.taskui)
}
}
The problem was a misunderstanding of angularjs scope. When using isolated scope, it is not enough to just bind a variable. You must also pass the value as an attribute. See solution #3 here: https://stackoverflow.com/a/17900556/555493
The code (using the original example) should be:
// component
export default {
bindings: {
taskui: '='
},
controller,
templateUrl: "component.html"
};
// parent template
<component taskui="taskui"></component>

Angular 1.5 component attribute presence

I'm refactoring some angular directives to angular 1.5-style components.
Some of my directives have behavior that depends on a certain attribute being present, so without the attribute having a specific boolean value. With my directives, I accomplish this using the link function:
link: function(scope,elem,attrs, controller){
controller.sortable = attrs.hasOwnProperty('sortable');
},
How would I do this with the angular 1.5-style component syntax?
One thing I could do is add a binding, but then I'd need to specify the boolean value. I'd like to keep my templates as-is.
Use bindings instead of the direct reference to the DOM attribute:
angular.module('example').component('exampleComponent', {
bindings: {
sortable: '<'
},
controller: function() {
var vm = this;
var isSortable = vm.sortable;
},
templateUrl: 'your-template.html'
});
Template:
<example-component sortable="true"></example-component>
Using a one-way-binding (indicated by the '<') the value of the variable 'sortable' on the controller instance (named vm for view model here) will be a boolean true if set as shown in the example. If your sortable attribute currently contains a string in your template an '#' binding may be a suitable choice as well. The value of vm.sortable would be a string (or undefined if the attribute is not defined on the component markup) in that case as well.
Checking for the mere presence of the sortable attribute works like this:
bindings: { sortable: '#' }
// within the controller:
var isSortable = vm.sortable !== undefined;
Using bindings may work but not if you are trying to check for the existence of an attribute without value. If you don't care about the value you can just check for it's existence injecting the $element on the controller.
angular
.module('yourModule')
.component('yourComponent', {
templateUrl: 'your-component.component.html',
controller: yourComponentsController
});
function yourComponentController($element) {
var sortable = $element[0].hasAttribute("sortable");
}
There is a built-in way to do this by injecting $attrs into the controller.
JS
function MyComponentController($attrs) {
this.$onInit = function $onInit() {
this.sortable = !!$attrs.$attr.hasOwnProperty("sortable");
}
}
angular
.module("myApp", [])
.component("myComponent", {
controller: [
"$attrs",
MyComponentController
],
template: "Sortable is {{ ::$ctrl.sortable }}"
});
HTML
<my-component sortable>
</my-component>
<my-component>
</my-component>
Example
JSFiddle

Resources