I'm building an Angular 2 app which would have a side nav bar for screens wider than 500, and a bottom nav bar for screens less wide than 500. For now I was trying to assign a 20% width to the side bar, 80% to app content.
The problem that I have is that the router-outlet content (i.e. the actual app) takes up the full width of the page instead of just 80%. It seems to be ignoring any styling I try to give it. Are we not supposed to style router-outlet directly? Or perhaps there is a better way that I'm overlooking?
app.component.ts
import { Component, HostListener } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<div class="container-fluid">
<nav *ngIf="this.window.innerWidth > 500"></nav>
<router-outlet style="width:80%;float:right;"></router-outlet>
<nav *ngIf="this.window.innerWidth < 500"></nav>
`,
styleUrls: ['app/app.component.css']
})
export class AppComponent {
window = [];
ngOnInit(): void {
this.window.innerWidth = window.innerWidth;
}
#HostListener('window:resize', ['$event'])
onResize(event) {
this.window.innerWidth = event.target.innerWidth;
console.log(this.window.innerWidth);
}
}
Simple solution is to just put <router-outlet> in a styled div:
<div style="width:80%;float:right;">
<router-outlet></router-outlet>
</div>
By using :host we can modify the style while loading the component.
:host(selector) { width:70% }
Following is the component:
import { Component, OnInit } from '#angular/core';
#Component({
selector: 'test-selector',
templateUrl: './test.component.html',
styleUrls: ['./test.component.css']
})
export class TestComponent{
}
//test.component.css content
:host(test-selector) { width:70% } will reduce the width to 70%
The key is /deep/ keyword:
:host /deep/ router-outlet + *:not(nav) {
width:80%;
float:right;
}
Since the component is dynamically loaded right after tag, the selector '+' matches anything next to it.
And the :not() selector excludes element in your template.
Edited 2018/3/1:
Since Angular 4.3.0 made /deep/ deprecated, its suggested alternative is ::ng-deep. And there were a long discussion about this.
Use host:{'style':'width:70%'} within #Component({}) in the component file to set the width of child
you could do the same with css grid. as width the accepted answer, it seems to only work in a surrounding div
Related
I have a component which is reusable. This component is called from parent component multiply times and sometimes the background page of the parent component is white and sometimes is black.
My child component generates form tags dynamycally - inputs,selects, textarea.
That means i can't have fixed styles in my css in my component for my content.
So when when the background page is white - i have one style for my inputs - for example black background. When the background page is black i have another style for my inputs - for example white bacgrkound.
To solve this is issue:
i tried
Adding input property in my child component ts file
#Input()
public cssTemplate;
in html
<div [ngClass]="{'form-group-white-bg': cssTemplate == 'white', 'form-group-dark-bg': cssTemplate == 'black'}">
<label for=""></label>
....
In the CHILD component i am sending value for input property depending on where the child component is called
if it is called on page with white background
<app-form-group cssTemplate="black" formControlName="address">
</app-form-group>
if it is called on black bacgrkound
<app-form-group cssTemplate="white" formControlName="address" [data]="{ field: 'address', label: 'Address' }">
</app-form-group>
but the problem here is that sometimes on my parent component this component is called multiply times
on one page can be called 12 times where i need 10 inputs and 2 selects
on other page can be called 15 times etc.
That means that i need to repat my self 15 times
<app-form-group cssTemplate="white" formControlName="address">
</app-form-group>
<app-form-group cssTemplate="white" formControlName="someItherControlName">
</app-form-group>
and everywhere to put cssTemplate="white".
ngFor is not an optin because this child component is called multiply times but not on same place in the HTML structure in the parent.
How can i solve this DRY?
you can add styles in your styles.css (the styles general for all the application). If e.g. you has
.white h1{
color:red;
}
.black h1{
color:green;
}
You can use [ngClass] in the "parent"
<div [ngClass]="toogle?'white':'black'">
<hello name="{{ name }}"></hello>
</div>
<button (click)="toogle=!toogle">toogle</button>
See [stackblitz][1]
NOTE: I used the way [ngClass]="expresion" (where expresion use conditional operator) better that [ngClass]="{'classname1':condition;'classname2':!condition}"
Update about your comment "how can i prevent repeating my self on child call", really I don't understand so much. I don't know if you want to make a directive like, e.g.
#Directive({
selector: 'hello', //<--the selector is the selector of the component
exportAs: 'helloDiv'
})
export class HelloDirective implements OnInit{
constructor(#Self() private component:HelloComponent,private dataService:DataService){
}
ngOnInit(){
console.log(this.component.name)
this.dataService.theme.subscribe(res=>{
this.component.theme=res;
})
}
}
This allow to "extends" the component -in the stackblitz the variable "theme" change-
[1]: https://stackblitz.com/edit/angular-ivy-sjwxyq?file=src%2Fapp%2Fapp.component.html
You can use an input property to create a css class map to pass on to ngClass. This object should be an object of string arrays.
It can be pretty much as complex and contain as many classes and rules as you need it too
#Input() color: 'white' | 'red' | 'hotpink' = 'white';
classMap: any;
ngOnInit() {
this.updateClassMap();
}
updateClassMap() {
this.classMap = {
[this.color]: !!this.color, // Add this class if not null
};
}
Then in the Html simply pass it to ngClass
<div [ngClass]="classMap">
Styling Child Components depending on Parent Component
There are two approaches I commonly take in this scenario
:ng-deep - create a style rule based on a class which is set in your parent component
utilize #ContentChildren() to set a property directly on your child components and call detectChanges() manually after the change.
To adopt the first solution you need to exercise greater care in your css naming rules, as using ng-deep obviously breaks the isolation of those style rules.
To adopt the second approach needs some considering due to it technically circumventing the standard input/output flow in Angular and thus can be a bit of a surprise "undocumented behavior" for any other maintainers of the application.
I'm a bit on the fence whether I prefer one approach over the other. The first approach seems more trivial to me, but it can also cause unintended style rule overwrites, while the second approach involves a lot more scripting and seems a bit of a hack.
Approach 1: ng-deep
Give your parent component an input and update a class on a block-element wrapping your <ng-content>.
create your desired style rules in your child component.
// parent component
#Component(...)
export class FooParent {
#Input() bgStyle: 'light' | 'dark' = 'light';
}
<!-- parent component template -->
<div class="parent" [ngClass]="{light: bgStyle == 'light', dark: bgStyle == 'dark'}">
<ng-content></ng-content>
</div>
// child.css
::ng-deep .light .child-container {
background-color: lightblue;
}
::ng-deep .dark .child-container {
background-color: royalblue;
}
My targeted element in the example is .child-container, you would write a similar style rule for each element you want to affect.
Approach 2: Using ContentChildren to pass along a value
Add a #ContentChildren() decorator to your parent component which selects for your child components.
inject a ChangeDetectorRef
implement ngAfterViewInit to loop through each child and set the value
call detectChanges() once done.
add the ngClass directive as normally in your child component.
Parent
#Component({
selector: 'parent',
templateUrl: 'parent.component.html',
styleUrls: ['parent.component.scss']
})
export class ParentComponent implements AfterViewInit, OnChanges {
#Input() bgStyle: 'light' | 'dark' = 'light';
#ContentChildren(ChildComponent) childComponents!: QueryList<ChildComponent>;
constructor(private change: ChangeDetectorRef) {
}
ngOnChanges(changes: SimpleChanges) {
if ('bgStyle' in changes) {
this.updateChildComponents();
}
}
updateChildComponents() {
this.childComponents.forEach(child => {
child.bgStyle = this.bgStyle;
});
this.change.detectChanges();
}
ngAfterViewInit() {
this.updateChildComponents();
}
}
<!-- parent.component.html -->
<ng-content></ng-content>
Child
#Component({
selector: 'child',
templateUrl: 'child.component.html',
styleUrls: ['child.component.scss']
})
export class ChildComponent implements OnInit {
bgStyle: 'light' | 'dark' = 'light';
constructor() {
}
ngOnInit(): void {
}
}
<!-- child.component.html -->
<div [ngClass]="{light: bgStyle == 'light', dark: bgStyle == 'dark'}" class="child-container"></div>
// child.component.css - you would apply styles as you needed obviously.
.child-container {
width: 40px;
height: 40px;
margin: .5rem;
}
.light.child-container {
background-color: lightblue;
}
.dark.child-container {
background-color: royalblue;
}
Usage
<!-- any other template -->
<parent>
<child></child>
<child></child>
<child></child>
</parent>
Note: If you are creating the ChildComponent directly in the ParentComponent's own template you need to use #ViewChildren instead of #ContentChildren
I'm working on a webpage that's based on Angular and uses Bootstrap. When I add an Angular module, a .scss file is created. However, even with my .ts file specifying my .scss as styleUrl, the page still uses the bootstrap reboot styles.
Specifically I'm trying to simply change the page's background colour right now but it seems not to be possible. All it changes is the area also defined by this weird jumbotron box.
Do I maybe have to specify in my html code that I want to use the style sheet? I thought I already did this by linking the stylesheet in my .ts.
Here's my code for the component.ts and component.scss, as well as a screenshot from the inspector in chrome:
.ts
import {Component, OnInit} from '#angular/core';
import {AuthService} from '../../services/auth.service';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
.scss
.jumbotron {
background: none
}
body {
background-color: #7EF911
}
How do I define my css as more important than bootstrap's default stuff?
Thank you very much!
To override Bootstrap style try using ng-deep
:host ::ng-deep .jumbotron {
background: none
}
:host ::ng-deep body {
background-color: #7EF911
}
How to force CSS of child component from parent using ::ng-deep or something?
I have parent component where I put child component:
....parent.component...
<app-likes></app-likes>
.....parent.component......
Not inside that likes component there is he following HTML:
<div class="mainDiv">
<div class="secondDiv"><i class="far fa-heart fa-3x"></i></div></div>
Now I want to set color of fa-heart class to white from parent parent.component.css.
How can I do that?
You can do this way, in the css of the parent component:
parent.component.css:
:host ::ng-deep .fa-heart {
color: red;
}
or
:host ::ng-deep app-likes .fa-heart {
color: red;
}
Well I will go against the folks above and suggest that you don't do this.
If you consider the component an isolated building block in your app, you would consider it an advantage, that it looks the same in every place you use it. Using ::ng-deep to override this behaviour will cause you trouble in larger apps.
Angular promotes using #Inputs as the interface of passing data into the component. My suggestion is to use #Input to modify the view. Or, if in larger contexts you can use Dependency Injection to provide a token that specifies a theme for all children of a component.
<app-likes theme="white"></app-likes>
#Component({selector: 'app-likes'})
class AppLikesComponent {
#Input() theme: string;
#HostBinging("class") get themeBinding() {
return 'theme--' + this.theme;
}
}
You could set the ViewEncapsulation option in the parent component to remove the shadow DOM. This essentially allows the child component to use the selector definitions from the parent component.
Try the following
Parent component
import { Component, ViewEncapsulation } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ],
encapsulation: ViewEncapsulation.None // <-- no shadow DOM
})
export class AppComponent {
}
Parent CSS
.fa-heart {
color: white;
}
Working example: Stackblitz
Background
I have a component that wraps two span elements. I would like parent components to be able to style the inner two spans by specifying CSS classes and styles as inputs (preferably as simple strings). Here's a simplified example of what I would like this to look like:
Code
app component (parent)
#Component({
selector: 'my-app',
template: `
<div>
<app-two-spans [classArg1]="'class1'" [classArg2]="'class2'"
[styleArg1]="'width: 100px'" [styleArg2]="'width:200px'"></app-two-spans>
</div>
`, style: `
::ng-deep .class1 {
border: 1px solid black;
}
::ng-deep .class2 {
border: 1px solid blue;
}
`
})
export class App {
}
two spans component (child)
import {Component, Input, Output, EventEmitter} from '#angular/core';
#Component({
selector: 'app-two-spans',
template: `
<span *ngIf="flag" [ngClass]="classArg1" [ngStyle]="styleArg1"
(click)="flag = !flag">click me</span>
<span *ngIf="!flag" [ngClass]="classArg2" [ngStyle]="styleArg2"
(blur)="flag = !flag" contenteditable="true">
click me to change my value</span>
`
})
export class TwoSpansComponent {
flag: boolean = true;
#Input() styleArg1: string;
#Input() styleArg2: string;
#Input() classArg1: string;
#Input() classArg2: string;
constructor() {}
}
Problem
The class styling seems to work in my local environment (though it doesn't seem to work on Plunker for some reason). However, the styles are not showing up. I've seen other posts about styles as inputs, but from what I've seen this is usually done by passing style-value pairs (see accepted answer here). However, I would really like to pass these styles as a simple string to make working with the component easier.
I notice the following error in the console: ERROR Error: Cannot find a differ supporting object 'width: 100px'. I'm not sure what this means at all.
Plunker here
Is there a way to do this? How can I give parent components the ability to stylize children?
ngStyle accepts an Object instead of plain string. You can pass your styles as:
[styleArg1]="{ width: '100px' }"
[styleArg2]="{ width: '200px' }"
How can I use Angular 2 styles in #Component with multiple classes on the same tag?
#Component({
styles: `.class1 .class2{background-color:red;}`
})
This generates the following css code:
.class1[<RANDOM_ANGULAR_ATTR>] .class2[<RANDOM_ANGULAR_ATTR>]{
background-color: red;
}
This will not select a tag defined like this:
<div class=".class1 .class2" RANDOM_ANGULAR_ATTR></div>
Is there any way to make this approach work?
Your styles should have the classes without a space between them. Multiple classes in the same selector must be written directly after each another – with no white space. For example:
.class1.class2 { background-color:red; }
And in order to select your object you should have the classes added like this:
<div class="class1 class2">Test css</div>
See the code below:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
styles: [
`
.class1.class2{background-color:red};
` ],
template: `
<h1>My First Angular 2 App</h1>
<div class="class1 class2">Test css</div>
`
})
export class AppComponent { }
For more options see also the following blog posts (about Shadow DOM, Encapsulation Types .. any other cool things):
https://scotch.io/tutorials/all-the-ways-to-add-css-to-angular-2-components
http://blog.thoughtram.io/angular/2015/06/29/shadow-dom-strategies-in-angular2.html
Have you looked at Justin Schwartzenberger's NG-Conf talk on CSS styles and the randomly generated tag? https://www.youtube.com/watch?list=PLOETEcp3DkCq788xapkP_OU-78jhTf68j&v=J5Bvy4KhIs0
potential options are to:
Use a styleUrls template
change encapsulation
use a different class (since its only relevant to this component its ok to name something simple vs using 2)