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 this component:
<styled-input
[(value)]="myValue"
validationDirective1
validationDirective2
required
email
></styled-input>
Internally:
<div>
<input
[value]="value"
(input)="valueChanged.emit($event.target.value)"/>
<ul>
<li *ngFor="let error of errors">{{ error }}</li>
</ul>
</div>
How would I pass the validation down into the styled-input component?
Once you declare the directive inside the app module. You can access that directive across whole application. Hence, directly point out directive inside child component (input element).
I have a simple directive like so in my HTML...
<div data-navigation>
<div class="pull-left"><img src="/images/my-site-logo-tiny.png"></div>
<div class="pull-right"><h2>My Site Name</h2></div>
</div>
and here is my JavaScript
.directive('navigation', function () {
'use strict';
return {
restrict: 'A',
replace: false,
transclude: true,
templateUrl: 'navigation.html',
link: function (scope, element) {
scope.showMenu = {
status: false
};
element.bind('click', function (){
scope.showMenu.status = !scope.showMenu.status;
console.log(scope.showMenu.status); // with each click this outputs correctly
});
};
});
Whilst in my navigation.html template I have the following, notice that I am writing showMenu.status to the view to see if this is updated...
<div data-ng-transclude></div>
{{ showMenu.status }}
<div data-ng-show="showMenu.status">
<ul>
<li>Menu Item</li>
<li>Menu Item</li>
<li>Menu Item</li>
<li>Menu Item</li>
<li>Menu Item</li>
<li>Menu Item</li>
<ul>
</div>
When I click on my directive the link function outputs the correct value of scope.showMenu.status but the showMenu.status is never updated in my template? It remains false regardless of clicking. What am I doing wrong?
So angular has something called Angular context, which gets "injected" into the browser event loop. When an event is fired, the javascript will run and then the appropriate DOM changes will be made.
That is the normal event loop. Having angular, when an event is fired and angular is notified, angular will manage that event, run angular stuff and when it is the turn to run the DOM updates, your bindings will get updated as well.
How is angular notified then? Calling $scope.$apply(). That is what kicks the angular context.
The click event of your example is a classic browser event and that is not managed by the Angular context so you have to notify angular
The proper way to do it is:
element.bind('click', function (){
scope.$apply(function() {
scope.showMenu.status = !scope.showMenu.status;
});
});
And never
element.bind('click', function (){
scope.showMenu.status = !scope.showMenu.status;
scope.$apply();
});
What you run inside an $apply is done inside a try - catch so if there is any error, the angular exception handler can be notified. That is really useful if you have some kind of notification (like a toast) if there is any error.
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
My question involves how to go about dealing with complex nesting of templates (also called partials) in an AngularJS application.
The best way to describe my situation is with an image I created:
As you can see this has the potential to be a fairly complex application with lots of nested models.
The application is single-page, so it loads an index.html that contains a div element in the DOM with the ng-view attribute.
For circle 1, You see that there is a Primary navigation that loads the appropriate templates into the ng-view. I'm doing this by passing $routeParams to the main app module. Here is an example of what's in my app:
angular.module('myApp', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when("/job/:jobId/zones/:zoneId", { controller: JobDetailController, templateUrl: 'assets/job_list_app/templates/zone_edit.html' }).
when("/job/:jobId/initial_inspection", { controller: JobDetailController, templateUrl: 'assets/job_list_app/templates/initial_inspection.html' }).
when("/job/:jobId/zones/:zoneId/rooms/:roomId", { controller: JobDetailController, templateUrl: 'assets/job_list_app/templates/room_edit.html' })
}]);
In circle 2, the template that is loaded into the ng-view has an additional sub-navigation. This sub-nav then needs to load templates into the area below it - but since ng-view is already being used, I'm not sure how to go about doing this.
I know that I can include additional templates within the 1st template, but these templates are all going to be pretty complex. I would like to keep all the templates separate in order to make the application easier to update and not have a dependency on the parent template having to be loaded in order to access its children.
In circle 3, you can see things get even more complex. There is the potential that the sub-navigation templates will have a 2nd sub-navigation that will need to load its own templates as well into the area in circle 4
How does one go about structuring an AngularJS app to deal with such complex nesting of templates while keeping them all separate from one another?
UPDATE: Check out AngularUI's new project to address this problem
For subsections it's as easy as leveraging strings in ng-include:
<ul id="subNav">
<li><a ng-click="subPage='section1/subpage1.htm'">Sub Page 1</a></li>
<li><a ng-click="subPage='section1/subpage2.htm'">Sub Page 2</a></li>
<li><a ng-click="subPage='section1/subpage3.htm'">Sub Page 3</a></li>
</ul>
<ng-include src="subPage"></ng-include>
Or you can create an object in case you have links to sub pages all over the place:
$scope.pages = { page1: 'section1/subpage1.htm', ... };
<ul id="subNav">
<li><a ng-click="subPage='page1'">Sub Page 1</a></li>
<li><a ng-click="subPage='page2'">Sub Page 2</a></li>
<li><a ng-click="subPage='page3'">Sub Page 3</a></li>
</ul>
<ng-include src="pages[subPage]"></ng-include>
Or you can even use $routeParams
$routeProvider.when('/home', ...);
$routeProvider.when('/home/:tab', ...);
$scope.params = $routeParams;
<ul id="subNav">
<li>Sub Page 1</li>
<li>Sub Page 2</li>
<li>Sub Page 3</li>
</ul>
<ng-include src=" '/home/' + tab + '.html' "></ng-include>
You can also put an ng-controller at the top-most level of each partial
Well, since you can currently only have one ngView directive... I use nested directive controls. This allows you to set up templating and inherit (or isolate) scopes among them. Outside of that I use ng-switch or even just ng-show to choose which controls I'm displaying based on what's coming in from $routeParams.
EDIT Here's some example pseudo-code to give you an idea of what I'm talking about. With a nested sub navigation.
Here's the main app page
<!-- primary nav -->
Page 1
Page 2
Page 3
<!-- display the view -->
<div ng-view>
</div>
Directive for the sub navigation
app.directive('mySubNav', function(){
return {
restrict: 'E',
scope: {
current: '=current'
},
templateUrl: 'mySubNav.html',
controller: function($scope) {
}
};
});
template for the sub navigation
Sub Item 1
Sub Item 2
Sub Item 3
template for a main page (from primary nav)
<my-sub-nav current="sub"></my-sub-nav>
<ng-switch on="sub">
<div ng-switch-when="1">
<my-sub-area1></my-sub-area>
</div>
<div ng-switch-when="2">
<my-sub-area2></my-sub-area>
</div>
<div ng-switch-when="3">
<my-sub-area3></my-sub-area>
</div>
</ng-switch>
Controller for a main page. (from the primary nav)
app.controller('page1Ctrl', function($scope, $routeParams) {
$scope.sub = $routeParams.sub;
});
Directive for a Sub Area
app.directive('mySubArea1', function(){
return {
restrict: 'E',
templateUrl: 'mySubArea1.html',
controller: function($scope) {
//controller for your sub area.
}
};
});
You may checkout this library for the same purpose also:
http://angular-route-segment.com
It looks like what you are looking for, and it is much simpler to use than ui-router. From the demo site:
JS:
$routeSegmentProvider.
when('/section1', 's1.home').
when('/section1/:id', 's1.itemInfo.overview').
when('/section2', 's2').
segment('s1', {
templateUrl: 'templates/section1.html',
controller: MainCtrl}).
within().
segment('home', {
templateUrl: 'templates/section1/home.html'}).
segment('itemInfo', {
templateUrl: 'templates/section1/item.html',
controller: Section1ItemCtrl,
dependencies: ['id']}).
within().
segment('overview', {
templateUrl: 'templates/section1/item/overview.html'}).
Top-level HTML:
<ul>
<li ng-class="{active: $routeSegment.startsWith('s1')}">
Section 1
</li>
<li ng-class="{active: $routeSegment.startsWith('s2')}">
Section 2
</li>
</ul>
<div id="contents" app-view-segment="0"></div>
Nested HTML:
<h4>Section 1</h4>
Section 1 contents.
<div app-view-segment="1"></div>
I too was struggling with nested views in Angular.
Once I got a hold of ui-router I knew I was never going back to angular default routing functionality.
Here is an example application that uses multiple levels of views nesting
app.config(function ($stateProvider, $urlRouterProvider,$httpProvider) {
// navigate to view1 view by default
$urlRouterProvider.otherwise("/view1");
$stateProvider
.state('view1', {
url: '/view1',
templateUrl: 'partials/view1.html',
controller: 'view1.MainController'
})
.state('view1.nestedViews', {
url: '/view1',
views: {
'childView1': { templateUrl: 'partials/view1.childView1.html' , controller: 'childView1Ctrl'},
'childView2': { templateUrl: 'partials/view1.childView2.html', controller: 'childView2Ctrl' },
'childView3': { templateUrl: 'partials/view1.childView3.html', controller: 'childView3Ctrl' }
}
})
.state('view2', {
url: '/view2',
})
.state('view3', {
url: '/view3',
})
.state('view4', {
url: '/view4',
});
});
As it can be seen there are 4 main views (view1,view2,view3,view4) and view1 has 3 child views.
You may use ng-include to avoid using nested ng-views.
http://docs.angularjs.org/api/ng/directive/ngInclude
http://plnkr.co/edit/ngdoc:example-example39#snapshot?p=preview
My index page I use ng-view. Then on my sub pages which I need to have nested frames. I use ng-include.
The demo shows a dropdown. I replaced mine with a link ng-click.
In the function I would put $scope.template = $scope.templates[0]; or $scope.template = $scope.templates[1];
$scope.clickToSomePage= function(){
$scope.template = $scope.templates[0];
};
Angular ui-router supports nested views. I haven't used it yet but looks very promising.
http://angular-ui.github.io/ui-router/