I'm learning AngularJS and noticed that Angular inserts template or partial HTML as a child of angular elements such as ng-include or custom directives. In my case this breaks styling because Bootstrap styles do not select elements that are children of the directive. So, I believe I'm either misunderstanding how to use directives or perhaps lack the terms for managing this behavior.
For example:
I want to specify just my custom directive and not associate a particular HTML type.
Input:
<ul class="nav navbar-nav">
<my-dropdown-messages/>
</ul>
Result:
<ul class="nav navbar-nav">
<my-dropdown-messages>
<li class="dropdown my-intended">content</li>
</my-dropdown-messages>
</ul>
Desired result:
<ul class="nav navbar-nav">
<li my-dropdown-messages class="dropdown my-intended">content</li>
</ul>
or
<ul class="nav navbar-nav">
<form my-dropdown-messages class="my-intended">content</form>
</ul>
Is it possible to configure ng-include or custom directives so that the insertion sets the native html type, and does not put the partial html as a child of the angular directive?
You can use an Attribute directive instead of a Element directive
app.directive('myDropdownMessages', function() {
return {
restrict: 'A', // Attribute directive
templateUrl: 'my-dropdown.html'
};
});
From AngularJS Docs
When should I use an attribute versus an element? Use an element when you are creating a component that is in control of the template. The common case for this is when you are creating a Domain-Specific Language for parts of your template. Use an attribute when you are decorating an existing element with new functionality.
https://docs.angularjs.org/guide/directive
using replace: true for your directive
but this property will be removed soon as very few scenarios where element replacement is required
refer to directive
Related
I'm learning Angular 12 and I have some issues about the framework operation.
I've created a new project, added Bootstrap 5 and created some components.
When I nest a component inside another like this :
<div class="container">
<div class="row">
<div class="col-xs-12">
<h2>Mes appareils</h2>
<ul class="list-group">
<app-appareil [appareilName]="appareilOne"></app-appareil>
<app-appareil [appareilName]="appareilTwo"></app-appareil>
<app-appareil [appareilName]="appareilThree"></app-appareil>
</ul>
</div>
</div>
</div>
I don't understand why I still see the custom selectors in the browser inspector view :
Angular browser view
It breaks several things in my Boostrap style.
Did you know if it's possible to hide/remove these custom components of my browser view to get in this case only the <li> tags directly inside the <ul> instead of these <app-appareil> ?
Thanks :)
Change
#Component({
selector: "app-appareil"
})
to
#Component({
selector: "li[appAppareil]"
})
and then use it as
<ul class="list-group">
<li appAppareil [appareilName]="appareilOne"></li>
</ul>
By using an attribute selector we can avoid the wrapping component tag (which you cannot "remove"), and we preserve semantics of the DOM itself.
Likely to get better semantics you'd want to make further changes and use content projection, but that's unclear from the limited information and beyond the scope of the question anyway.
To make it the "Angular way", the approach needs to be changed.
Supposing that you have a collection of device names (appareilNames) returned from your component:
public get deviceNames(): Array<string> { ... }
The appropriate tags structure can be achieved as follows:
<ul class="list-group">
<li *ngFor="let deviceName of deviceNames"> <!-- iterate on each device name -->
<app-appareil [appareilName]="deviceName"></app-appareil> <!-- use each name to create a component with it -->
</li>
</ul>
I have an Angular 1.5.3 component that appears to not update the values for a two way binding. My controller changes the values which are passed to the component.
The component appears to read the default values when the controller is initialized but thereafter acts as if it is one way bound. Any future changes to the bound values are not read in the component.
I converted this from a similar functioning directive and the two way binding worked just fine. Is there an on change event, or something similar, I’m missing for components? Do I need to add specific logic to the component controller so the component template can read the bound values?
Menu template that implements the component:
<div data-ng-controller="MenuCtrl as ctrl">
<!-- below shows ctrl values updating when controller changes them -->
<pre>{{ctrl.menu}}</pre>
<pre>{{ctrl.settings}}</pre>
<!-- changes not reflected in component -->
<my-sub-menu menu="ctrl.menu" settings="ctrl.settings"></my-sub-menu>
</div>
Sub menu component:
(function () {
'use strict';
angular
.module('myApp.components')
.component('mySubMenu', {
bindings: {
menu: '=',
settings: '='
},
templateUrl: 'subMenu.component.html',
controller: function () {
// implementation that reads menu and settings
}
});
})();
Simplified sub menu component template:
<ul>
<li ng-show="settings.menu1">Menu 1</li>
<li ng-show="settings.menu2">Menu 2</li>
<li ng-show="settings.menu3">Menu 3</li>
</ul>
<!-- changes to bound values not reflected in component template -->
<pre>{{menu}}</pre>
<pre>{{settings}}</pre>
As long as you don't have controller alias for your component, you could use default controllerAs alias as $ctrl. You could override it by having controllerAs option in component definition object.
Markup
<ul>
<li ng-show="$ctrl.settings.menu1">Menu 1</li>
<li ng-show="$ctrl.settings.menu2">Menu 2</li>
<li ng-show="$ctrl.settings.menu3">Menu 3</li>
</ul>
<pre>{{$ctrl.menu}}</pre>
<pre>{{$ctrl.settings}}</pre>
I'm using an angular directive and I am not having any luck with the jQlite .find() method:
DIRECTIVE
function cardsList () {
return {
restrict: 'A',
controller: 'CardsController',
templateUrl: 'app/directives/cards-list/cards-list.html',
link: function ($scope, $element, attr, CardsController) {
var cardLink = $element.find('a');
console.log(cardLink);
});
}
}
}
contextCards.directive('cardsList', cardsList);
An empty [] gets logged on the console.
TEMPLATE
<li data-ng-repeat="card in cards" class="cards--item">
<a class="cards--link" data-ng-href="#/{{ card.slug }}">{{ card.title }}</a>
</li>
VIEW
<ul class="col-xs-12 cards--list" cards-list></ul>
All I want to do is traverse to the <a> elements. According to the docs, .find() only works on tag names which is exactly what I'm trying to do.
EDIT: I want to add a class to the <a></a> if the card the link represents is selected (like .current-card)
From your answer it's not clear how the selected card is specified in the model, so I am assuming that the card object (the object of each iteration of ng-repeat) holds this flag, for example: card.isSelected.
Then, you could use ng-class to specify which CSS class to add based on this value:
<li ng-repeat="card in cards" class="cards--item">
<a class="cards--link"
ng-class="{'current-card': card.isSelected}"
ng-href="#/{{ card.slug }}">{{ card.title }}</a>
</li>
Addendum:
The answer to your original question about why .find("a") returns empty, it is because ngRepeat directive transcludes its content (which means that Angular takes the elements out of DOM during compilation), and places it at a later stage than your link function.
I'm trying to use data binding inside a <script>.
<ol class="breadcrumb"></ol>
<div class=heading><h2>{{current.name}}</h2>
...
<script>
$(function(){
$(".breadcrumb").append('<li> Ledige stillinger</li>');
$(".breadcrumb").append('<li><a href=#!/categories/{{current.keyname}}>{{current.name}}</a></li>');
});
</script>
</div>
My result is:
Ledige stillinger (this is ok!) / {{current.name}} (here should it be the name, it works inside h2)
What am I doing wrong?
my answer would be to use AngularJS as it is intended and 1-way bind your DOM to the model values.
if there are multiple elements with the "breadcrumb" class then add these to a collection in your controller's scope and use ng-repeat directive to render them all out then append the "li" tags to this template.
Please see this plunkr example: http://plnkr.co/edit/0T6ldRzSTCnVIKnn3WRh
look at app.js and the following markup:
<ul class="breadcrumb">
<li>{{current.name}}</li>
</ul>
I am trying to conditionally display a directive based on a boolean value stored in the parent scope. I can't figure out why the below does not work. By, "not work" I mean neither directives are displayed.
<ul class="nav navbar-nav pull-right" ng-switch="userIsAuthenticated">
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in anonymousMenuItems" ng-switch-when="false"></account-item>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in authenticatedMenuItems" ng-switch-when="true"></account-item>
</ul>
Neither directives are shown even thought "userIsAuthenticated" is set to 'false' in my test case. If I add {{userIsAuthenticated}} above the directives 'false' is output as expected.
I've also tried this:
<ul class="nav navbar-nav pull-right" ng-switch={{userIsAuthenticated}}>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in anonymousMenuItems" ng-switch-when={{false}}></account-item>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in authenticatedMenuItems" ng-switch-when={{true}}></account-item>
</ul>
If I remove the conditional ng-switch-when attribute from either of the directives they will display. So I'm know the problem is my ng-switch.
Your usage of ng-switch works in this simplified demo, of course without your account-item directive:
http://plnkr.co/AppN8xmFeIwjaP631lj7
Without seeing the code for account-item, it is hard to guess what might be interfering with it. You might consider using ng-if to handle displaying one item or another.
<ul>
<div ng-if="!userIsAuthenticated">Content when not authenticated</div>
<div ng-if="userIsAuthenticated">Content when authenticated</div>
</ul>
Update
Also make sure you bind to an object property, instead of a primitive boolean. Like: user. authenticated
Since ngSwitchWhen has a priority of 800, you need to set a higher priority to your custom directive (i.e. account-item) in order for it to be compiled before being process by the ngSwitchWhen directive. E.g.:
.directive('accountItem', function () {
return {
...
priority: 900,
...
};
});