Is it possible to style the same component different in Angular 2? - javascript

I have a generic component I want to re-use throughout my app. The problem is that I want to style it differently for various parts of the site. Is this possible?
I'm guessing there's a way to pass in a path for the styleUrl, but that seems really messy and i'm hoping there's a better alternative.
I also tried this but it didn't work: When specifying the component, add in the class, something like this
<generic-component class="customStyle1"></generic-component>
and then added styling based on customStyle1 into generic-component's stylesheet, but it didn't seem to pick up on the style.

You may use :host-context in the style to theme your component based upon some class applied where it is being used.
Read more about it here!!
test.css
:host-context(.theme-green) h3 {
background-color: green;
}
:host-context(.theme-red) h3 {
background-color: red;
}
app.component
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<h3 class="title">Basic Angular 2</h3>
<my-test class="theme-green" ></my-test>
<my-test class='theme-red' ></my-test>
`
})
export class AppComponent {
constructor(){}
}
test.component
#Component({
selector: 'my-test',
template: `<h3>Test Component</h3>
`,
styleUrls : ['./app/test.css']
})
export class TestComponent {
constructor(){}
}
Here is the Plunker!!
Hope this helps!!

You can use the :host(...) selector combined with #HostBinding() like:
#Component({
selector: 'my-comp',
styles: `
:host([type-default]) {
background-color: red;
}
:host([type-header]) {
background-color: blue;
}
:host([type-main]) {
background-color: green;
}
`
})
export class MyComponent {
#Input()
#HostBinding('component-type')
componentType:String="type-default"
}
and then switch style like
<header>
<my-comp componentType="type-header"></my-comp>
</header>
<main>
<my-comp componentType="type-main"></my-comp>
</main>
<my-comp></my-comp>
You can also apply a class from the outside like in your question and then use the :host(...) selector like
:host(.customStyle1) {
Then you don't need this part
#Input()
#HostBinding('component-type')
componentType:String="type-default"
but this way might be beneficial if you want to combine styling with other configuration settings for the component.

Related

How does Angular Component CSS encapsulation work?

I want to understand that if I create two style sheets
Style 1
.heading {
color: green;
}
Style 2
.heading {
color: blue;
}
Now if these two styles are written in two different views, when rendering them
on a layout as a Partial View, then in this case a conflict could occur
and one could override the style of the other.
BUT
Using angular(see page 16), how come these two styles in different components render on the same page with encapsulation? How come the CSS is not overriden?
For example
import { Component } from '#angular/core';
#Component({
selector: 'app-user-item',
template: '<p class="heading">abc</p>',
styleUrls: ['./user-item.css']
})
export class UserItemComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
user-item.css
.heading{ color :green}
app-user.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-user',
template: '<p class="heading">abc</p>',
styleUrls: ['./user.css']
})
export class UserItemComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
user.css
.heading{ color :blue}
When rendering this on a page:
<app-user></app-user>
<app-user-item></app-user-item>
This is the result:
Angular (by default) emulates a shadow DOM.
It dynamically creates some HTML attributes that are only applicable to elements in that component.
For example:
<app-user></app-user>
<app-user-item></app-user-item>
will be transformed to
<app-user _ngcontent-1></app-user>
<app-user-item _ngcontent-2></app-user-item>
And your css will be transformed to:
.heading[_ngcontent-1] { color: blue }
.heading[_ngcontent-2] { color: green }
You can find a more complete explanation here and the documentation here
Angular comes with CSS encapsulation out of the box. When generating a new project, the default is for the styles.css file at the root of the project to apply globally to the application, and for component styles, such as the styles found in foo.component.css,to only apply to the foo component and nowhere else. But that is not the only way styles can be encapsulated in Angular, let us take a closer look.#Component({
selector: 'app-foo',
templateUrl: './foo.component.html',
styleUrls: ['./foo.component.css']
})

is there an event to detect a class attribute change on a template element in angular?

If you use a framework such as bootstrap, you know that an element classes change depending on actions or viewport size...
Is there an event in Angular that allows us to detect when the classes of an element change?
For example, having a bootstrap navbar, each time it has show class in its class list I want to console.log("show"), and each time it doesn't have show class in its class list I want to console.log("hide"). In other words, I want to subscribe to the class change of the element.
You could detect a class change by using EventEmitter to emit the change. You'll need to subscribe to it before emitting. See the following example, hope that helps:
import { Component, EventEmitter } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<p [ngClass]="{ active: active }">
The active text
</p>
<button (click)="isActive($event)">change class</button>
`,
styles: [`
.active {
background-color: yellow;
}
`]
})
export class AppComponent {
active = false;
activate: EventEmitter<boolean> = new EventEmitter();
isActive(event) {
this.active = !this.active;
this.activate.emit(this.active);
}
ngOnInit() {
this.activate.subscribe((data) => {
console.log('active: ' + data);
})
}
}

How can I apply custom CSS to a Toast with ng2-izitoast?

I want to apply a custom Style to the Toasts I show with izitoast in my Angular application.
I have installed and included the library according to instructions here
I have included the izitoast css and script in my angular.json in addition to adding the module in my app-module and also managed to display a toast with a button in a component:
html-template (toaster.component.html)
<p>toaster works!</p>
<div><button (click)="produceToast()">Test Toast</button></div>
corresponding typescript file (toaster.component.ts)
import { Component, OnInit } from '#angular/core';
import {Ng2IzitoastService} from "ng2-izitoast";
#Component({
selector: 'app-toaster',
templateUrl: './toaster.component.html',
styleUrls: ['./toaster.component.scss']
})
export class ToasterComponent implements OnInit {
constructor( public iziToast: Ng2IzitoastService) { }
ngOnInit() {
}
public produceToast() {
this.iziToast.show({ title: "Hello World"});
}
}
I understand that if I want to apply custom styling I have to somehow specify the class in the object I pass into the the show()-function, but how do I do that? Writing a CSS-class in my toaster.component.css and just referencing the name doesn't work:
.myOwnToastClass {
background-color: blueviolet;
color: white; //font-color
}
Adding the class into my styles.css doesn't work either. Neither
{class: "myOwnToastClass", title: "Hello World"} nor {class: ".myOwnToastClass", title: "Hello World"} do anything.
Can someone tell me how to pass my own custom CSS to a toast? The documentation simply says "class: The class that will be applied to the toast. It may be used as a reference." but other than that there is no documentation on how to use it.
Okay. So thanks to user fridoo I was able resolve it.
You have to add the class to your styles.css and be careful about the !important modifier:
.myOwnToastClass {
background-color: blueviolet !important;
border-radius: 0 !important;
//color: white; // you can't change the font-color with this
// you have to use the object-properties in the ts-file
}
Then reference it in the typescript files like so:
public produceToast() {
this.iziToast.show({class: "myToastClass", title: "Hello World", timeout: 3000, titleColor: "#ffffff"});
// titleColor: "white" would also work, I think it's a css-class somewhere in the background
}

Angular add style tag into innerHTML

I create an Angular project and I wanted to insert style CSS into the html, but I don't want the inserted CSS replace other style that have the same tag or class name.
const testStyle = '<style>
body {color: red}
table {width : 200px}
h1{font-size:12px}
.another-same-class-name{color: blue;font-size: 13px}
</style>'
Above is my sample style that I want to insert into my component template
My Component
#Component({
selector : 'app-my-component',
templateUrl: './my-template.component.html',
styleUrls: ['./my-style.component.scss'],
})
export class MyComponent{
myStyle:string
...
ngOnInit(){
const testStyle = '<style>
body {color: red}
table {width : 200px}
h1{font-size:12px}
.another-same-class-name{color: blue;font-size: 13px}
</style>'
this.myStyle = testStyle
}
updateCss(newCss){
this.myStyle = `<style>${newCss}</style>`
}
}
<div #styleWillGoHere [innerHtml]="myStyle"></div>
Edit: I have update my question, to make it more clear :)
I appreciate any kind of solution.
You need to use DomSanitizer of #angular/platform-browser to sanitize the HTML. Take a look of the docs: https://angular.io/api/platform-browser/DomSanitizer.
In your particular case, you need to use bypassSecurityTrustHtml() method. Also, for apply styles only to one component, add an id to your target component and use it in your styles. (You can use class if that component will appear more than once in your web).
EXAMPLE:
import { Component } from '#angular/core';
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
#Component({
selector: 'my-app',
template: `
<div id="myId">
<div [innerHtml]="myStyle"></div>
<h1>Hello</h1>
</div>
`
})
export class AppComponent {
myStyle: SafeHtml;
constructor(private _sanitizer: DomSanitizer) { }
ngOnInit() {
this.myStyle =
this._sanitizer.bypassSecurityTrustHtml(`<style>#myId h1{color: red}</style>`);
}
}
DEMO: https://stackblitz.com/edit/angular-3kza7c?file=src%2Fapp%2Fapp.component.ts

multiple projections of same content using <ng-content> [duplicate]

depending on the value of a (boolean) class variable I would like my ng-content to either be wrapped in a div or to not be wrapped in div (I.e. the div should not even be in the DOM) ... Whats the best way to go about this ? I have a Plunker that tries to do this, in what I assumed was the most obvious way, using ngIf .. but it's not working... It displays content only for one of the boolean values but not the other
kindly assist
Thank you!
http://plnkr.co/edit/omqLK0mKUIzqkkR3lQh8
#Component({
selector: 'my-component',
template: `
<div *ngIf="insideRedDiv" style="display: inline; border: 1px red solid">
<ng-content *ngIf="insideRedDiv" ></ng-content>
</div>
<ng-content *ngIf="!insideRedDiv"></ng-content>
`,
})
export class MyComponent {
insideRedDiv: boolean = true;
}
#Component({
template: `
<my-component> ... "Here is the Content" ... </my-component>
`
})
export class App {}
Angular ^4
As workaround i can offer you the following solution:
<div *ngIf="insideRedDiv; else elseTpl" style="display: inline; border: 1px red solid">
<ng-container *ngTemplateOutlet="elseTpl"></ng-container>
</div>
<ng-template #elseTpl><ng-content></ng-content> </ng-template>
Plunker Example angular v4
Angular < 4
Here you can create dedicated directive that will do the same things:
<div *ngIf="insideRedDiv; else elseTpl" style="display: inline; border: 1px red solid">
<ng-container *ngTemplateOutlet="elseTpl"></ng-container>
</div>
<template #elseTpl><ng-content></ng-content></template>
Plunker Example
ngIf4.ts
class NgIfContext { public $implicit: any = null; }
#Directive({ selector: '[ngIf4]' })
export class NgIf4 {
private context: NgIfContext = new NgIfContext();
private elseTemplateRef: TemplateRef<NgIfContext>;
private elseViewRef: EmbeddedViewRef<NgIfContext>;
private viewRef: EmbeddedViewRef<NgIfContext>;
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<NgIfContext>) { }
#Input()
set ngIf4(condition: any) {
this.context.$implicit = condition;
this._updateView();
}
#Input()
set ngIf4Else(templateRef: TemplateRef<NgIfContext>) {
this.elseTemplateRef = templateRef;
this.elseViewRef = null;
this._updateView();
}
private _updateView() {
if (this.context.$implicit) {
this.viewContainer.clear();
this.elseViewRef = null;
if (this.templateRef) {
this.viewRef = this.viewContainer.createEmbeddedView(this.templateRef, this.context);
}
} else {
if (this.elseViewRef) return;
this.viewContainer.clear();
this.viewRef = null;
if (this.elseTemplateRef) {
this.elseViewRef = this.viewContainer.createEmbeddedView(this.elseTemplateRef, this.context);
}
}
}
}
Remember that you can put all this logic in separate component! (based on yurzui answer):
import { Component, Input } from '#angular/core';
#Component({
selector: 'div-wrapper',
template: `
<div *ngIf="wrap; else unwrapped">
<ng-content *ngTemplateOutlet="unwrapped">
</ng-content>
</div>
<ng-template #unwrapped>
<ng-content>
</ng-content>
</ng-template>
`,
})
export class ConditionalDivComponent {
#Input()
public wrap = false;
}
You can then use it like this:
<div-wrapper [wrap]="'true'">
Hello world!
</div-wrapper>
I checked into this and found an open issue on the subject of multiple transclusions with the tag. This prevents you from defining multiple tags in a single template file.
This explains why the content is displayed correctly only when the other tag is removed in your plunker example.
You can see the open issue here:
https://github.com/angular/angular/issues/7795

Categories