I am working on an app which has a main view, and inside it renders several named child router outlets. The code is as follows:
app.component.html:
<router-outlet />
app.routes.ts:
export const routes: Routes = [
{ path: '', component: SlotsView, children: [
{
path: '',
outlet: 'slotA',
component: RouterOutlet1,
},{
path: '',
outlet: 'slotB',
component: RouterOutlet2,
}
]
},
];
slots.view.html:
This is a view with some child routes
<br>
<router-outlet name="slotA"></router-outlet>
<br>
<router-outlet name="slotB"></router-outlet>
RouterOutlet1:
I am Router outlet 1
:host{
background-color: #f88;
}
@Component({
templateUrl: './router-outlet-1.router-outlet.html',
styleUrls: ['./router-outlet-1.router-outlet.less'],
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true
})
// eslint-disable-next-line @angular-eslint/component-class-suffix
export class RouterOutlet1{
}
RouterOutlet2:
I am Router outlet 2
:host{
background-color: #88f;
}
@Component({
templateUrl: './router-outlet-2.router-outlet.html',
styleUrls: ['./router-outlet-2.router-outlet.less'],
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true
})
// eslint-disable-next-line @angular-eslint/component-class-suffix
export class RouterOutlet1{
}
Note that the difference between both router outlets is that router outlet 1 should have a reddish color (#f88
), and router outlet 2 should have a bluish color (#88f
).
This is what actually happens:
In DevTools I see that both host elements have the same attribute (_nghost-ng-c4243086843
)
And that's why the same style is applied to both of them.
If I change the encapsulation
on both router outlets to ShadowDom
, the result is rendered correctly:
Is this a bug in Angular which should be reported to the developers, or is there a good reason for Router outlet 1's styles to be applied to Router outlet 2?
I already did a bit of googling and haven't found such a bug report.
I tried changing the encapsulation
mode.
None
no styles are applied with the :host
rule as explained here: Is it possible to style using :host with Encapsulation.None in Angular?ShadowDom
it works correctly, but I don't want to use it unless it is necessaryAfter checking the replicated Stackblitz from the user, the below change fixes the issue.
Since the same ng-component
is present for both router-outlets we are getting this issue, This definitely looks like a bug in Angular, because each element should have their own encapsulation if they are from different outlets.
To solve this issue, we need to use the host styling directly on the element so that even if the attributes match, the direct application of the styles ensures the colors are applied to the correct elements
Snippet:
...
host: {
['style.backgroundColor']: '#f88',
},
...
outlet 1
import {
ChangeDetectionStrategy,
Component,
OnInit,
ViewEncapsulation,
} from '@angular/core';
@Component({
templateUrl: './router-outlet1.component.html',
styleUrls: ['./router-outlet1.component.less'],
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
['style.backgroundColor']: '#f88',
},
standalone: true,
})
export class RouterOutlet1Component implements OnInit {
constructor() {}
ngOnInit() {}
}
outlet 2
import {
ChangeDetectionStrategy,
Component,
OnInit,
ViewEncapsulation,
} from '@angular/core';
@Component({
templateUrl: './router-outlet2.component.html',
styleUrls: ['./router-outlet2.component.less'],
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
['style.backgroundColor']: '#88f',
},
standalone: true,
})
export class RouterOutlet2Component implements OnInit {
constructor() {}
ngOnInit() {}
}
Note: This output is coming from ng-component
which is not even a proper component, you can just redirect the url to accommodate both the named router outlets and the components will get rendered instead of ng-component
.
It looks like a bug but I don't think it's replicable in the latest version of Angular (17).
Here is a Stackblitz that shows the issue not happening:
To solve this issue, you can add a host class on the component decorator, this will ensure the class gets applied on the correct host.
outlet 2
import {
ChangeDetectionStrategy,
Component,
OnInit,
ViewEncapsulation,
} from '@angular/core';
@Component({
selector: 'app-router-outlet2',
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
I am Router outlet 2
`,
styles: `
:host.outlet2 {
background-color: #88f;
}
`,
host: {
'[class.outlet2]': 'true',
},
standalone: true,
})
export class RouterOutlet2Component implements OnInit {
constructor() {}
ngOnInit() {}
}
outlet 1
import {
ChangeDetectionStrategy,
Component,
OnInit,
ViewEncapsulation,
} from '@angular/core';
@Component({
selector: 'app-router-outlet1',
template: `
I am Router outlet 1
`,
encapsulation: ViewEncapsulation.Emulated,
changeDetection: ChangeDetectionStrategy.OnPush,
styles: `
:host.outlet1 {
background-color: #f88;
}
`,
host: {
'[class.outlet1]': 'true',
},
standalone: true,
})
export class RouterOutlet1Component implements OnInit {
constructor() {}
ngOnInit() {}
}