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

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.

Related

angular 1.5 component / default value for # binding

Is there any way to specify the default value for an # binding of a component.
I've seen instruction on how to do it with directive: How to set a default value in an Angular Directive Scope?
But component does not support the compile function.
So, I have component like this:
{
name: 'myPad',
bindings : {layout: '#'}
}
I want to free users of my component from having to specify the value of the 'layout' attribute. So..., this:
<my-pad>...</my-pad>
instead of this:
<my-pad layout="column">...</my-pad>
And... this 'layout' attribute is supposed to be consumed by angular-material JS that 'm using, so it needs to be bound before the DOM is rendered (so the material JS can pick it up & add the corresponding classes to the element).
update, some screenshots to clarify the situation:
Component definition:
{
name : 'workspacePad',
config : {
templateUrl: 'src/workspace/components/pad/template.html',
controller : controller,
bindings : {
actions: '<', actionTriggered: '&', workspaces: '<', title: '#',
flex: '#', layout: '#'
},
transclude: {
'workspaceContent': '?workspaceContent'
}
}
}
Component usage:
<workspace-pad flex layout="column" title="Survey List" actions="$ctrl.actions"
action-triggered="$ctrl.performAction(action)">
<workspace-content>
<div flex style="padding-left: 20px; padding-right: 20px; ">
<p>test test</p>
</div>
</workspace-content>
</workspace-pad>
I want to make that "flex" and "layout" in the second screenshot (usage) optionals.
UPDATE
My "solution" to have this in the constructor of my component:
this.$postLink = function() {
$element.attr("flex", "100");
$element.attr("layout", "column");
$element.addClass("layout-column");
$element.addClass("flex-100");
}
I wish I didn't have to write those last 2 lines (addClass)... but well, since we don't have link and compile in component.... I think I should be happy with it for now.
First of there is great documentation for components Angularjs Components`. Also what you are doing I have done before and you can make it optional by either using it or checking it in the controller itself.
For example you keep the binding there, but in your controller you have something like.
var self = this;
// self.layout will be the value set by the binding.
self.$onInit = function() {
// here you can do a check for your self.layout and set a value if there is none
self.layout = self.layout || 'default value';
}
This should do the trick. If not there are other lifecycle hooks. But I have done this with my components and even used it in $onChanges which runs before $onInit and you can actually do a check for isFirstChange() in the $onChanges function, which I am pretty sure will only run once on the load. But have not tested that myself.
There other Lifecycle hooks you can take a look at.
Edit
That is interesting, since I have used it in this way before. You could be facing some other issue. Although here is an idea. What if you set the value saved to a var in the parent controller and pass it to the component with '<' instead of '#'. This way you are passing by reference instead of value and you could set a watch on something and change the var if there is nothing set for that var making it a default.
With angularjs components '#' are not watched by the component but with '<' any changes in the parent to this component will pass down to the component and be seen because of '<'. If you were to change '#' in the parent controller your component would not see this change because it is not apart of the onChanges object, only the '<' values are.
To set the value if the bound value is not set ask if the value is undefined or null in $onInit().
const ctrl = this;
ctrl.$onInit = $onInit;
function $onInit() {
if (angular.isUndefined(ctrl.layout) || ctrl.layout=== null)
ctrl.layout = 'column';
}
This works even if the value for layout would be false.
Defining the binding vars in constructor will just initiate the vars with your desired default values and after initialization the values are update with the binding.
//ES6
constructor(){
this.layout = 'column';
}
$onInit() {
// nothing here
}
you can use
$onChanges({layout}) {
if (! layout) { return; }
this.setupLayout(); ---> or do whatever you want to do
}

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.

angularJS 1.5 nested components

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

Move function from controller to directive

I have a function in my controller that manipulates the DOM. I understand this to be a bad practice and DOM manipulations should be moved into a directive. I'm having trouble pulling it out of the controller and into its own directive.
I have the following example code in my controller:
$scope.sidebarToggle = function() {
if ($scope.request = null) {
$(#container).switchClass('bottom', 'top', 400, 'linear');
$scope.editing = true;
}
else {
$(#container).switchClass('top', 'bottom', 400 'linear');
$scope.editing = false;
{
};
The above code's if conditions are very simplified, in the live code there are multiple conditions. Otherwise an ng-show/hide directive might have been possible.
The purpose of the code is to recognize the state the user is in, reveal/hide an off-screen sidebar (the class assignments), and set the 'editing' state of the controller.
How can this be refactored into a directive to accomplish the same goal?
Take a look at the documentation for angular directives to get started.
https://docs.angularjs.org/guide/directive
For 'angularising' the class of the container, use ng-class.
example # Adding multiple class using ng-class
You probably don't need to create your own directive for that.
Angular have already created some directives that could help you out.
In your case, you should use angular directive : ng-show and ng-class or ng-style
Exemple :
HTML
<div ng-show="request == null"> Edit </div>
<div ng-class="{'class-top': request == null,'class-bottom' : request != null}"> <div>
CSS :
.class-top{
...
}
.class-bottom{
...
}
Let me know if it works for you,
Nico
Try this:
app.directive('test', function() {
return {
restrict: 'E',
scope: {
},
link: sidebarToggle
};
});
I think it's creating a directive and link to your function sidebarToggle

resulting HTML from $compile(custom-directive) doesn't bind {{values}}

I want to dynamically add Angular custom Directives, but the directive resulting from $compile(directive) doesn't have the 2-ways binding.
Here's my simplified problem: I am using MapBox, and I want to use Directives for the the markers' popup to show, for example, the markers' title. MapBox wants HTML as a String to put inside the popup, so my idea was to pass a $compiled directive, something like $compile('<myDirective></myDirective>')($scope).html().
It replace the directive with its template, but {{values}} are not solved.
I have something like this to create the popup
map.featureLayer.on('layeradd', function(e)
{
var marker = e.layer;
var popupContent = ctrl.createPopup(marker);
// popupContent should be HTML as String
marker.bindPopup(popupContent);
});
ctrl.createPopup(marker) call a function of the controller, that does:
this.createPopup = function(marker)
{
var popup = "<mapbox-marker-popup"
+" title = "+marker.feature.properties.title
+"</mapbox-marker-popup>";
// should return HTML as String
return ($compile(popup)($scope).html());
}
where mapbox-marker-popup is a directive specified as follow:
/* ===== MARKER POPUP DIRECTIVE=========== */
.directive('mapboxMarkerPopup', function() {
return {
restrict: 'E',
template: [
"<p>{{title}}</p>",
].join(""),
scope:
{
title: '#'
}
}
})
Anyway... mapboxMarkerPopup is not working. title is shown as {{title}}
[UPDATE2 - {{title}} not solved]
Here's the JSFiddle
You need to return the compile angular element instead of returning html of that element. Only returning the html will never carry the angular two way binding. By using compiled object you can keep your binding working.
Code
this.createPopup = function(marker) {
var popup = "<mapbox-marker-popup" +
"title = '" + marker.feature.properties.title + "'"
+ "</mapbox-marker-popup>";
return ($compile(popup)($scope)[0]);
};
Working Fiddle
Update
$compile
Compiles an HTML string or DOM into a template and produces a template
function, which can then be used to link scope and the template
together.
Take a look at this link will give you more idea

Resources