Can I change a style of multiple components with one toggle - html

I am trying to achieve day/night feature with a click of a toggle on my web app.
I know how to add it to a single component with my nav menu lets say,
but I need to add it to multiple components.
One of the solutions I found is to use ng-deep but it feels a bit wrong to do it that way in the main CSS.
Another solution I have figured out is to create a service and subscribe to the toggle in each of the components, but again that feels like an overkill.
My questions is: Can I change a style of multiple components with one toggle?
Would prefer not to use JS.
Currently, my app.component looks like this
import { Component, ElementRef } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.less']
})
export class AppComponent {
title = 'Optimus Engine';
version = 'Latest';
day = true;
constructor(private element: ElementRef) {
}
dayNight() {
if (!this.day) {
this.element.nativeElement.classList.add('night');
}
if (this.day) {
this.element.nativeElement.classList.remove('night');
}
this.day = !this.day;
//BTW for some reason it does not remove the class but that's a different problem.
}
}
And then on my template:
<div class="toggle-box" (click)="dayNight()">
<input type="checkbox" name="checkbox1" id="toggle-box-checkbox" />
<label for="toggle-box-checkbox" class="toggle-box-label-left"></label>
<label for="toggle-box-checkbox" class="toggle-box-label"></label>
</div>
And then in less:
:host.night h1 {
transition: all 1s;
color: aliceblue;
}
But this works only for h1 and I'm wondering how to got other components to change with that toggle.

You can use a service to propagate a value accross multiple components.
For instance, a service like this:
export class ThemeService {
private modeSubject: BehaviorSubject<'day'|'night'> = new BehaviorSubject<'day'|'night'>('day');
public get mode():Observable<'day'|'night'>{
return this.modeSubject.asObservable();
}
public switchMode(newMode:'day'|'night'):void{
this.modeSubject.next(newMode);
}
}
And then, in your component, simply subscribe to the mode observable:
...
...
constructor(themeService: ThemeService){
themeService.mode.subscribe(mode => this.theme = mode);
}
...
...
finally, use [ngClass] to bind the theme to your template:
Any component implementing this logic will switch with your theme mode (day or night).
Keep in mind that I used two strings here but you can use an enum for sure, these were just for the example.

Related

Host Binding rearranges applied classes

I am using #HostBinding('class') to inject classes into the host element. The classes to be injected are generated based on user-supplied parameters. The problem I ran into and I could not find anyone else experiencing is that the classes are applied in an order different from the way I expected them.
For example, having a component defined below:
import {Component, HostBinding, Input} from '#angular/core';
#Component({
selector: '[icon]',
template: `
<ng-content></ng-content>
`
})
export class SuiIconComponent {
#Input() iconType = '';
#HostBinding('class')
get classes(): string {
return [this.iconType, 'icon'].join((' '));
}
}
When I apply the component like shown below:
<div icon iconType="car"></div>
And inspect, I see <div class="icon car"></div> instead of the appropriately formatted <div class="car icon"></div>.
I have tried reversing the array before joining but that did not help either.
Is there any way I get the classes to get rendered in the proper order?
Edit: I realized the classes are being rearranged in alphabetic order.
I'm not sure why angular changes the order, but you can solve your problem with little bit of change in your template.
#Component({
selector: 'icon',
template: `
<div [ngClass]="iconType + ' icon'">
<ng-content></ng-content>
</div>
`
})
export class SuiIconComponent {
#Input() iconType = '';
}
and use it as follows
<icon iconType="car">
Some content here
</icon>

Computationally bind styles in Angular

Using Angular5 - I know how it is possible to bind an HTML element's style to a Boolean value, but I can't find an explanation to do this for multiple styles at the same time.
ie. I have found something like this works fine:
[style.background]="r.favourite === true ? '#3f51b5' : 'white'"
However I am also wanting to change the color of my text to white at this point also... And I don't want to clutter my components with lots of [style.xxx] tags.
Is there a way I can dynamically bind to a CSS class to apply when r.favourite === true?
I have seen ways in which this can be done... However this assumes you are binding within the same file as such:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<button class="my-btn" [class.extraclass]="someProperty">Call to Action</button>
`,
styles: [`
.my-btn { font-size:1.7em; }
.extraclass { background: black; color: white; }
`]
})
export class AppComponent {
someProperty = true;
}
However my CSS is being stored in a shared file - such that I have a file structure like:
import { Component } from '#angular/core';
#Component({
selector: 'my-component',
styleUrls: ['./css/shared-styles.css']
template: `
<button class="my-btn" [class.extraclass]="someProperty">Call to Action</button>
`
})
export class MyComponent {
someProperty = true;
}
You can use NgClass.
<div [ngClass]="{'text-success':r.favourite ,'text-danger':!r.favourite}">
Where 'text-success' and 'text-danger' are classes you define.
Please refer to this great article about NgClass and NgStyle:
https://codecraft.tv/courses/angular/built-in-directives/ngstyle-and-ngclass/
Hope this helps

Changing AngularDart Component colour

I'm trying to change a tab-button element colour via CSS, Upon inspection I noticed that it has class tab-button, and so in CSS I'm doing:
.tab-button {
color: lightcoral;
}
but nothing seems to be changing.
I'm probably barking up completely the wrong tree as my CSS experience is exactly nil, but I'm at a loss
Edit:
app_component.html
<!DOCTYPE html>
<html>
<link href="app_component.css" rel="stylesheet">
<body>
<h1 id="introHeading">
Hello Angular!
</h1>
<material-tab-panel id="tabPanel">
<material-tab label="One" id="tabOne">
Tab One
</material-tab>
<material-tab label="Two">
Tab Two
</material-tab>
</material-tab-panel>
<material-checkbox class="checkboxClass" themeColor="#F08080" id="testCheckbox" label="Test" (handleClick)="checkChecked()"></material-checkbox>
<material-button label="Click me!" id="buttonToClick"></material-button>
</body>
app_component.dart
import 'package:angular2/angular2.dart';
import 'package:angular_components/angular_components.dart';
#Component(
selector: 'my-app',
styleUrls: const ['app_component.css'],
templateUrl: 'app_component.html',
directives: const [materialDirectives, MaterialCheckboxComponent],
providers: const [materialProviders],
)
class AppComponent implements OnInit {
#override
ngOnInit() {
}
}
I am unable to see where you are including the tab-button class to the HTML page. Is there another html page which corresponds to the class you are referring to? In order to have the class take effect you need to include class="tab-button" within the opening tag.
If you notice that this is still not working, there may be another class overriding the color which you are trying to use. You can add the important tag to make your color take precedence.
.tab-button {
color: lightcoral !important;
}

How to style a sibling component in Angular2?

I have a search component containing a logo, a searchbar and a routeroutlet. The execution of a search navigates to the resultlist, which is the pseudo html outlined here:
<search>
<logo></logo>
<searchbar></searchbar>
<result-list></result-list>
</search>
I like to style logo and searchbar differently on the results page so I tried to select the logo with :host >>> logo and the /deep/ alternative from the result-listcomponent. That doesn't work. Is there a way to select siblings?
Here a small plnkr to demonstrate the problem. http://plnkr.co/edit/Q0CLjcsftbqe0kHKrKks?p=preview Here I would like to style from resultlist the logo and the searchbarto be black.
A Similar solution to the one from Jens Habegger using :host-context(myCssClass) and a conditional. The style needs to be added to the logo and the searchbar component.
<search>
<logo [class.myCssClass]="isSearchResultList"></logo>
<searchbar [class.myCssClass]="isSearchResultList"></searchbar>
<result-list></result-list>
</search>
:host-context(.myCssClass) {
color: black;
}
What you are attempting is basically sharing global application state isSearchResultList: boolean across multiple components.
The obvious naive solution would be to hold the state at the respective shared parent component, and set it based on the current router-outlet.
<search>
<logo [isSearchResultList]="isSearchResultList"></logo>
<searchbar [isSearchResultList]="isSearchResultList"></searchbar>
<result-list></result-list>
</search>
You can use services for communication between components and ngClass for dynamic styling.
notification.service.ts
import {Injectable, EventEmitter} from '#angular/core';
#Injectable()
export class NotificationService {
private static _emitters: { [ID: string]: EventEmitter<any> } = {};
static get(ID: string): EventEmitter<any> {
if (!this._emitters[ID]) {
this._emitters[ID] = new EventEmitter();
}
return this._emitters[ID];
}
}
When sibling component run send a message.
bar.component.ts
import { NotificationService } from 'notification.service';
....
ngOnInit() {
NotificationService.get('barcomponent').emit(true);
}
ngOnDestroy() {
NotificationService.get('barcomponent').emit(false);
}
...
Listen to incoming messages from your component.
foo.component.ts
import { NotificationService } from 'notification.service';
....
ngOnInit() {
NotificationService.get('barcomponent').subscribe(value => {
this.activateStyle = value;
});
}
....
You can apply any class via ngClass
foo.component.html
....
<div [ngClass]="{'my-css-class':activateStyle}">
...
</div>
....

Styled HTML content dynamically switched with tabs using Angular 2

I am attempting to create a reusable angular2 component that accepts an array of URLs to html files on my server and creates a content window with tabs to switch between "chapters", effectively swapping out the html and css inside the content window. I have tried all sorts of things including iframes but those don't work, the angular 1 ng-include work-arounds that I can find on StackOverflow but they have all since been deprecated, and the closest I've got is building a component that you can #Input html and it interpolates the content but style won't apply and angular strips out any style or script tags. Here is what I have tried.
In my parent component class:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "h1 { color:red; }"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
My HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]=html></div>', //This works but no style
//template: '{{html}}', //This displays the actual markup on page
styles: ['{{css}}'] //This does nothing
//styles: ['h1 { color: red; }']//Also nothing
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}
The result of this code is
Why Does Angular make this so hard?
But no red color. Maybe style is applied before the innerHtml is added to DOM? I don't know but just putting {{html}} results in displaying the actual markup with the h1 tags visible.
The reason I want to do it this way is that I have a bunch of HTML pages already created sitting in a folder on my server from before I angularized my site that all share a single style sheet. I'd like to just be able to flip through them like pages in a book without reloading the page and since there are so many and I'm likely to add more all the time, I'd really rather not create routing for every single one. (I already have routing for basic site navigation.)
Does anybody have a better suggestion for how to embed styled HTML into a page dynamically in the most recent version of Angular 2? At the time of this post we are in 2.0.0-beta.17.
OR... I already figured I may be approaching this issue from the entirely wrong angle. There must be a reason Angular is making this so difficult and deprecating all the solutions people have come up with so If anyone has a suggestion about how I could achieve the same results in a more angular friendly way I'd love to hear that too.
Thank you.
Edit:
I was able to fix my issue by creating a pipe which sanatizes the html before adding it to an iframe.
import { Pipe, PipeTransform } from '#angular/core';
import { DomSanitizer } from '#angular/platform-browser';
#Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
transform(url: string) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
}
And then you can just pass your html into the iframe.
<iframe width="100%" height="1000" frameBorder="0" [src]="url | safe"></iframe>
This is useful to me since I have some old pages that use all sorts of jquery and style etc. This works as a quick fix to have them show up.
Angular2 rewrites the styles added to a component by including the dynamically added attributes like _ngcontent-yle-18 into the CSS selectors.
Angular2 uses this to emulate shadow DOM style encapsulation. These attributes are not added to dynamically added HTML (for example with innerHTML).
Workarounds
add styles to index.html because these styles are not rewritten by Angular2
set ViewEncapsulation.None because then Angular doesn't add the encapsulation emulation attributes
use /deep/ to make Angular2 ignore the encapsulation emulation attributes
See also Angular 2 - innerHTML styling
You should wrap your css into an object and use ngStyle to bind it to your component rather than the styles attribute, because styles does not support data binding.
Example:
htmlInput: string = "<h1>Why Does Angular make this so hard?</h1>";
cssInput: string = "{h1 { color:red; }}"
Parent Component HTML:
<app-html [html]='htmlInput' [css]='cssInput'></app-html>
Your HTML Component:
import { Component, Input, OnInit } from '#angular/core';
#Component({
selector: 'app-html',
template: '<div [innerHtml]="html" [ngStyle]="css"></div>',
styles: []
})
export class HtmlComponent implements OnInit {
#Input() html: string = "";
#Input() css: string = "";
ngOnInit() {
}
}