How to style a sibling component in Angular2? - html

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>
....

Related

Angular show fetched data after click dropdown button

I use angular at the frontend and .net core at the backend. I have a trivial problem and figure it with a video below. I spend hours and hours and still couldn't solve the problem.
I simply fetch data from my web api. And use *ngFor for display the data. But the problem is data is not shown before click any dropdown button.
Btw, dropdown button does not have any click event. It's simple language selector.
When I click language selector dropdown button, it expands and at the same time my data display on the screen.
I get my data ngOnInit. I check the data on debug mode and it's ok. I really spend hours and hours...still couldn't find any solution.
My html code :
<div class="container">
<div *ngFor="let d of devices"> --> I put here breakpoint and it run when I click dropdown btn
<span>{{d.name}}</span>
</div>
</div>
My .ts code:
import { ChangeDetectorRef, Component, OnInit } from '#angular/core';
import { Router } from '#angular/router';
import { ToastrService } from 'ngx-toastr';
import { Observable } from 'rxjs';
import { Pagination } from '../../../_helpers/Pagination';
import { deviceListModel, NewDeviceModel } from '../../admin-panel.model';
import { AdminPanelService } from '../../admin-panel.service';
#Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: ['./test.component.scss']
})
export class TestComponent implements OnInit {
devices : Array<deviceListModel>; ---> I also try with public deviceList : deviceListModel[]; nothing change
constructor(private service : PanelService, private router : Router) { }
ngOnInit(): void {
this.getDeviceList();
}
getDeviceList() {
this.service.getDeviceList().subscribe(res => {
this.devices = res;
})
} ---> This part works fine. Data comes true from the backend before click the dropdown button.(I checked in debug mode with breakpoints)
}
Visiulize my problem with below link;
https://media.giphy.com/media/jYGHN1Ndxyeqhtn0ZR/giphy.gif
fullscreen with low res : https://giphy.com/gifs/jYGHN1Ndxyeqhtn0ZR/fullscreen
Edit :
When I try just display one of the data like devices[0].name;
<div>
<span> {{devices[0}.name}} </span>
</div>
I get data on page but with an error in console.
The error comes three times and the error is ;
core.js:4352 ERROR TypeError: Cannot read property '0' of undefined
Try putting the this.devices = res; into the NgZone
constructor(..., private _ngZone: NgZone)
this._ngZone.run(() => { this.devices = res; });
Can you try to put an *ngIf in the container? Like so:
<div class="container" *ngIf="devices?.length">
<div *ngFor="let d of devices">
<span>{{d.name}}</span>
</div>
</div>
In this case, the template will only be rendered when the backend call has completed successfully (and has results).

Angular 10. How to navigate to a component's sections via a different component's anchor link tag?

app.component.html
<!--I wish to keep this structure like this, because menu.component has position sticky at top: 0-->
<app-home></app-home>
<app-menu></app-menu>
<app-about></app-about>
<app-projects></app-projects>
<app-contact></app-contact>
<app-footer></app-footer>
menu.component.html
<!--On anchor link click, navigate to corresponding component-->
<nav>
<a>Home</a>
<a>About</a>
<a>Projects</a>
<a>Contact</a>
</nav>
home.component.html, about.component.html, projects.component.html, contact.component.html
<!--Basic structure-->
<section>
<p>component works!</p>
</section>
A Link to the basic structure of the web-site
https://stackblitz.com/edit/angular-ivy-ufce4g
I would have shared a link to the actual web-site(my personal web-site), but i don't know if i am allowed.
One solution would be to create a shared service between you menu component and your app component.
It would work like this:
Dispatch a notification when a navigation item is clicked in you menu component;
Listen for notification in your app component. When a new notification is received, scroll to the corresponding component on your page.
Something like this:
#Injectable({
providedIn: 'root'
})
export class NavigationService {
private _navigation$ = new BehaviorSubject<string>('home');
navigation$ = this._navigation$.asObservable();
constructor() { }
updateNavigation(item) {
this._navigation$.next(item);
}
}
In your menu component, call the updateNavigation method of NavigationService whenever a navigation item is clicked.
#Component({
selector: 'app-menu',
// ...
})
export class MenuComponent {
constructor(private navigationService: NavigationService) {}
// Call this method whenever one of your navigation items is clicked
// in your template.
//
// navigationItem could, for example, be a string/enum corresponding to
// the component that must be scrolled into view
handleNavigationItemClick(navigationItem: string) {
this.navigationService.updateNavigation(navigationItem);
}
}
Finally, in your app component, listen for a navigation item update and scroll to the component.
#Component({
// ...
})
export class AppComponent implements OnInit {
constructor(private navigationService: NavigationService) {}
ngOnInit() {
this.navigationService.navigation$.pipe(
distinctUntilChanged(),
).subscribe(navigationItem => this.scrollIntoView(navigationItem))
}
private scrollIntoView(component) {
// Scrolling logic ...
}
}

Angular 7 image backgroud for View composed of 2 or more components?

this is my html:
<app-navbar></app-navbar>
<router-outlet></router-outlet>
I want to set an image as background for the home view that is divided in 2 components (navbarComponent and homeComponent -selector ) but this will not be aplied in the others component (e.g contactComponent -supose to use other image, with the selectors app-navbar, and app-contact).
So how can I use an image as backgroud for the view composed by the app-navbar and app-home ???
Why not to add the background image as a CSS class for your <body> tag that you can define in the global styles.css file and then under the page/route y'd like this to show up, add that class name to the <body> tag using one of these two options.
The first option is the easiest but less recommended:
constructor(#Inject(DOCUMENT) private document: Document) {}
ngOnInit(){
this.document.body.classList.add('test');
}
The second option (my personal recommendation) is to implement the custom rendering class from the #angular/core package:
import { Component, OnDestroy, Renderer2 } from '#angular/core';
export class myCoolComponent implements OnDestroy {
constructor(private renderer: Renderer2) {
this.renderer.addClass(document.body, 'test');
}
ngOnDestroy() {
this.renderer.removeClass(document.body, 'test');
}
Hope this will help you!

Can I change a style of multiple components with one toggle

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.

What is the React equivalent of an Angular directive that only works on attributes?

For example you could have a directive in angular like so:
angular.module('app')
.directive('classy', function() {
return {
restrict: 'A',
link: function($scope, $el) {
$el.addClass('stay-classy');
}
}
}
And implement like so:
<div classy></div>
There doesn't seem to be an equivalent in React that I've seen after reading through most the docs and googling. I was hoping for something like:
...
render: function() {
return (
<MyComponent classy></MyComponent>
);
}
Is there something like that possible that I've been missing? Is there a different yet functionally similar equivalent? Or maybe this question just shows that I'm missing some part of the "React way" and I shouldn't ever want to do this. Thanks!
It will be helpful to consider what Angular and React are each doing "behind the scenes."
In your Angular example, when you write <div classy/></div> you're saying "render a DIV element and then attach to it the behaviors defined by the classy directive.
In your React example, when you write <MyComponent classy></MyComponent>, you're saying, "create an instance of MyComponent and pass it the props { classy: true }. The transpiler (Babel or whathaveyou) will turn it into the following JavaScript:
React.createElement(MyComponent, { classy: true });
So the answer to your question is that you can't write <MyComponent classy></MyComponent> because MyComponent component doesn't know what to do with the classy prop. In React, you might write something like this instead:
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
This works because we know the React.DOM.div component (like most DOM components) knows what to do with the className prop.
Since React 0.14 we can express something like this more simply, as a "pure" stateless functional component, i.e. a function that accepts props and returns the rendered result:
function AlsoClassyDiv(props) {
const { className, ...rest } = props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
};
You can see both approaches in action in the below snippet.
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
function AlsoClassyDiv({ className, ...props }) {
return <div className={`${className || ''} stay-classy`} {...props}/>;
};
ReactDOM.render(
<div id="container">
<div>Regular div</div>
<ClassyDiv>ClassyDiv!</ClassyDiv>
<AlsoClassyDiv>AlsoClassyDiv!</AlsoClassyDiv>
</div>,
document.body
);
.stay-classy { font: bold 3em Helvetica; text-shadow: 4px 4px 2px #aaa; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
One way you could implement similar behavior is using React class mixins
A great example of a useful directive in angular is
Target
The smoothScroll directive would intercept the click event then use window scroll or jquery scrollTo to apply all manner of animation.
Anywhere in the html one could then simply use the directive powered class name.
This sort of thing is not available in React. To do it in React you would have to create a special link component to use instead of:
<a> like ASmooth....
I was looking to find a way to reproduce the directive system for applying style or play with the component.
You can create a component that play with children and then render them :
function TextCenter(props) {
// Iterates over children and clone it with custom props
const children = React.Children.map(
props.children,
(child) => React.cloneElement(child, { className: 'text-center' }
)
// Render the children
return <>{children}</>;
}
function MyComponent() {
return (
<TextCenter>
<div>
<h1>Hello centered world</h1>
<p>Yessss</p>
</div>
</TextCenter>
)
}
Here is a more powerfull example for responsive text alignement :
interface Props extends Breakpoints<'start' | 'center' | 'end'>{}
export const TextAlign: FunctionComponent<Props> = (props) => {
const className = generateClassName('text', props);
const children = React.Children.map(props.children, child => React.cloneElement(child as ReactElement, { className }))
return (
<>
{children}
</>
)
}
export const MyComponent: FunctionComponent<Props> = (props) => {
return (
<div>
<TextCenter xs="center" md="start">
<h1>I am centered on mobile but not on desktop</h1>
</TextCenter>
</div>
)
}
There are two problems with this solution, when the children is a component, it must also have the prop className and it also makes the HTML less clean as it adds a level in hierarchy.
Look my friend i didn't get you well but long story short, angularJS directives is actually a component. So the idea behind angularJs directive is to create component that has its own scope data and it's own method to operate on it. I was thinking the same way you did and found your post here and i couldn't find an answer for that. But thanks for working experience, i thought about it and know how to do it.
I wanted to add an edit button for each link item in a list to toggle the edit form for each one only so each ListItem should be a stand alone component, that way i have standalone state for each one and i toggle it on & off.