Customize component in Angular2/4 depending on where it is used - javascript

I have the following components in my application:
NavbarComponent
HomeComponent
ClassroomComponent
I use NavbarComponent in both HomeComponent and ClassroomComponent like this:
// home.component.html
<app-navbar></app-navbar>
..something related to HomeComponent
// classroom.component.html
<app-navbar></app-navbar>
..something related to ClassroomComponent
I want to customize navbar depending on where it is used: for example, if it's used in HomeComponent, I want it to be red, and if otherwise, then set its color to green.
Is there any way I can do this?

You can try to use the :host-context selector to define the component styles based on it's ancestor.
For your example it should be working like this:
// inside app-navbar css file
// where 'home-component' is the selector of your HomeComponent
:host-context(home-component) {
background: red; // NavbarComponent is red when inside HomeComponent;
}
:host-context(classroom-component) {
background: blue; // NavbarComponent is blue when inside ClassroomComponent;
}
More about special CSS selectors can be found here.

You can change your component in different ways
By using Provider(injection) to access global values
By using Event (publisher/subscriber methodology) procedure http://learnangular2.com/events/
You can define a #Input decorator in the NavbarComponent https://github.com/ionic-team/ionic/issues/5741 or http://www.concretepage.com/angular-2/angular-2-custom-event-binding-eventemitter-example

Related

Styles are not applied to HTML element after dynamically inserted to DOM - Angular

I wanted to add an "i" element as a child element to HTMLDivElement
but styles are not applied to the newly inserted "i" element.
How can I make sure to apply the same styles to newly added "i" element?
I want to apply the the styles that I have set in the scss file for the new element.
(when I am inserting the new element in the .ts file, I can set the same styles using JS(TS), but I intend to use the scss/css styles, is there any way to use the scss/css styles for the newly added elements?)
accommodation-view-data.html
<div class="star-rating" #starRating>
<i class="bi bi-star-fill"></i> <!-- styles are applied to this element successfully -->
</div>
accommodation-view-data.component.scss
i{
font-size: 1.6rem;
padding: 0 0.25rem ;
color: #ffbe0f;
}
accommodation-view-data.component.ts
import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '#angular/core';
#Component({
selector: 'app-accommodation-view-data',
templateUrl: './accommodation-view-data.component.html',
styleUrls: ['./accommodation-view-data.component.scss']
})
export class AccommodationViewDataComponent implements OnInit, AfterViewInit {
#ViewChild('starRating')
starRatingElem!: ElementRef;
constructor() { }
ngOnInit(): void {
}
ngAfterViewInit(): void {
/* When I append the the new element to the DOM,
* the styles that I have set in the scss file, are not applied for the new element */
const starIconElement = document.createElement('i');
starIconElement.classList.add('bi');
starIconElement.classList.add('bi-star-fill');
(this.starRatingElem.nativeElement as HTMLDivElement).append(starIconElement);
}
}
This is a normal behavior, When you write style that is integrated in the styleUrls, every single class is reworked to be specific to the component, it's the style scope (note the i[ng-content-lep-c52]in the screenshot below).
So adding an element in the dom like you do is not recommended, instead prefer toggling it thanks to a ngIf or here for stars with a ngFor or else you'll have hard times trying to make it dynamic and bind values with the component model.
Like you can see in this screenshot, the first element is properly matched with the scoped style while the new element isn't.
NOT RECOMMENDED AND DEPRECATED: To bypass this you can use the ::ng-deep pseudo class to make a style global, and therefor getting overrided by scoped style as you can see in this screenshot comming from this stackblitz.
You can find more information about styles in angular here

Accessing ViewChildren/ContentChildren in a structural Directive

I would like to create a parent directive which shows or hides children based on the values of the children. To do this, i've taken the approach of a parent structural directive, and a child directive with values. For simplicity without the values:
<div *appParent>
<div appChild>1</div>
<div appChild>2</div>
<div appChild>3</div>
<div appChild>4</div>
</div>
To access the children, I use the following line in the parent directive:
#ContentChildren(AppChildDirective, { read: AppChildDirective, descendents: true }) children: QueryList<AppChildDirective>;
This query list is always empty. However, when I change it to a non-structural, it works fine. Stackblitz demo here
I assume this is due to the fact the structural directive creates a parent ng-template, which #ContentChildren then looks inside to find the original component, meaning that the query actually goes nowhere.
What approach can I take to access the children of the original component and not the template? Or do I need to take another approach to handle my requirements?
ContentChildren seem to not work on structural directives. However, this can be achived by injecting the parent directive in the child and then registering the child in the parent by calling a function.
#Directive({
selector: '[appChild]'
})
export class ChildDirective {
constructor(parent: ParentDirective) {
parent.registerChild(this);
}
}
#Directive({
selector: '[appParent]'
})
export class ParentDirective {
registerChild(child: ChildDirective) { /*...*/ }
}
Side notes
If you also want to be able to use the child directive without the parent directive, change the child's constructor like this to make the parent optional:
constructor(#Optional() parent: ParentDirective) {
parent?.registerChild(this);
}
You can also use this approach recursively by injecting a directive in its own constructor. If you do so, also add #SkipSelf() in the constructor to really get the parent:
#Directive({
selector: '[appRecursive]'
})
export class RecursiveDirective {
constructor(#Optional() #SkipSelf() parent: RecursiveDirective) {
parent?.registerChild(this);
}
registerChild(child: RecursiveDirective) { /*...*/ }
}

How to apply Style to custom component content in Angular?

I am trying to apply styling to a child of a custom component.
Selector:
<custom-component-example></custom-component-example>
Inside custom-component-example.html:
<button>
<ng-content></ng-content>
</button>
If I were to use style like this:
<custom-component-example style="color: green">some text</custom-component-example>
Or like this:
<custom-component-example [ngStyle]="{'color': green}">some text</custom-component-example>
The button text will not get green. The styling could be anything (for example font-weight or size or anything really).
I have also tried the solution to this topic:
Best way to pass styling to a component
But that also does not apply to the child element (button in the example above).
How do I pass any given styling and apply it to the child element, in the case of the example, how would I pass styling (through the custom-component-example selector) and apply it to the button and the button's text?
Thanks in advance.
You should never alter the child style from the parent, instead here is what you should do :
Apply a class to the parent (let's say green-button).
In the child's css you need to check that does my parent has a class green-button, if yes then it should change it's color.
In the child's css file ->
:host-context(.green-button) button{
color : green
}
You should not transfer styles from parent to child essentialy because it spoils the ViewEncapsulation goodness that Angular is proud of.
Here is some refrence . : Link
Also, the child component should be responsible for how does it button looks. The parent should be concerned about itself. In the future, if you will have two children to your parent, it will be difficult to manage what style to pass to what child.
Using this method, altering style is not only easy but also manageable.
Upvote and mark as solved if I was able to help.Cheers.
You need to pass the style property to the child component using the #Input() like
Your child component HTML code should look like
<div [className]="tableCss">
</div>
Your child component ts file code shoule look like
#Input() tableCss: string;
Your Parent component should look like
<app-list [tableCss]="'table-responsive table-borderless table-grid mt-4 border-top border-primary'"></app-list>
Try to change styles into [styles]
custom-component-example.html
<button [ngStyle]="styles">
<ng-content></ng-content>
</button>
custom-component-example.ts
#Input() styles: any = {};
Use,
<custom-component-example [styles]="{color: green}">some text</custom-component-example>
If you would like to use input and styles without deep selectecting of css like that:
a > b > c {
color: green;
}
Change this class to this:
class CustomComponentExample {
#Input() styles;
}
Set styles for this input:
<custom-component-example [styles]="{'color': green}">some text</custom-component-example>
Use this property in your component:
<button [ngStyle]="styles">
<ng-content></ng-content>
</button>
Try this:
Add a class on that component into your template file like this example bellow. (class='toggle-button').
<custom-component-example class="toggle-button"> Button name </custom-component-example>
Use ::ng-deep to your scss file for styling this class and add !important to that parameter.
::ng-deep .toggle-button { .switch-btn {
width: 80px !important;
height: 40px !important;}}
*"switch-btn" class is a class into the parent component.

How can I toggle font size on click JavaScript / Angular

There are 4 steps there:
1.) Click on category
2.) Show filtered products
3.) Select filtered products
4.) Display selected products in most right part of screen /3rd child component/
What I would like to achieve is next:
When I click on product (3rd step), product is added to 'right' component, and there I would like to change a font size of quantity so it might look like animation, like make font bigger for example 28 and make it small again for example 18.
Products are added to the 3rd component by using service which is shared between child components. This is how it looks :
Thanks guys
Cheers
First of all, add a new rule to the order-quantity-number class:
transition: font-size 1s;
then define another selector in css:
.order-quantity-number.selected {
font-size: 48px;
}
then basically you just need to add this 'selected' class to the span element and the font-size will be animated. After 1s (anim is completed), you need to remove the class from the element and the text will shrink. I hope it answers the question :)
EDIT: Implementation details
Template:
Add reference to the span element so that it is accessible from code
<span class="order-quantity-number" #ref>{{receiptItem.quantity}}</span>
ts:
Add the following line to the class to use 'ref' from the template.
#ViewChild('ref') elRef: ElementRef;
Add setTimeout() call to the click handler that triggers the animation to remove selected class after 1s:
onClick() {
...
// 1. add 'selected' class to the span element
this.elRef.nativeElement.classList.add('selected');
// 2. remove it after 1s
setTimeout(() => {
this.elRef.nativeElement.classList.remove('selected');
}, 1000);
}
You can write simple #Directive that implements AfterViewInit interface in which you will add a class with bigger font-size and then watch for event transitionend and remove class.
something like this
#Directive({
selector: `[fontAnimation]`
})
export class FontAnimationDirective implements AfterViewInit {
constructor(
private hostElement: ElementRef
) { }
ngAfterViewInit() {
const el = this.hostElement.nativeElement as HTMLElement;
el.classList.add('animate-font-size');
el.addEventListener('animationend', (ev: TransitionEvent) => {
if (ev.propertyName == 'font-size') {
el.classList.remove('animation-font-size');
}
})
}
}
Warning: transitionend will trigger event for every property that has transition, so we need to check if propertyName is equal to font-size.
All you need to do is create proper css class. Don't forget to import it to proper NgModule

How to add several directives to a single component

Hello first of all I must say I am sorry but I don't know how to express the question better, is the reason I am not being able to find an answer by myself.
What I'm talking about is how to load a component inside another one, I need to indicate it in the directive. Here is a very small example that I did from scratch because I am not able to find the right syntax:
http://plnkr.co/edit/gFsqGJmmayOsewL3EfLf
import {Component} from 'angular2/core'
import {Prueba} from './prueba'
#Component({
selector: 'my-app',
providers: [],
template: `
<div>
<h2>Hello {{name}}</h2>
<prueba></prueba>
</div>
`,
directives: [Prueba]
})
export class App {
constructor() {
this.name = 'Angular2'
}
}
So as you can see in app.ts there is a directives inside component, if I remove that it does not work. I am not 100% sure why, but it's how I learned.
So next step, I wanted to have several components, so I can have Prueba and another that adds something extra (for starters, another "phrase", but the idea is to add something similar to THIS: http://plnkr.co/edit/SVPNwk?p=preview ). However I find myself unable to find the right syntax, anything I try makes even this simple example to fail.
As I said, I do not understand what am I missing, I have a new component, I import it, I use the selector, and so on, but it simply explodes. What concepts am I missing?
If I am still not explaining myself properly enough, this is the theoric concept I am talking about:
angular.io/docs/ts/latest/cheatsheet.html (I cannot post more than two links... anyway its the #Component part, that's the documentation I'm checking out).
In Angular2 there is a difference between a component and a directive:
A component gathers a view (template) with some properties and processing (the component class)
There are two kinds of directives:
Attribute directive. It changes the appearance or behavior of a DOM element
Structural directive. It changes the DOM layout by adding and removing DOM elements.
A component can be used in another component using its selector. You need to explicitly define it in the directives attribute of the container component. Whereas the attribute is called directives, you can put in it both components and directives. You can also provide parameters to a component and react on its events.
Here is a sample:
Sub component
#Component({
selector: 'sub',
template: `
<div>Sub</div>
`
})
export class SubComponent {
}
Container component:
#Component({
selector: 'comp',
template: `
<div>
<sub></sub>
</div>
`,
directives: [ SubComponent, AnotherComponent ]
})
export class ContainerComponent {
}
A directive will apply on an existing element also based on it selector.
Here is a sample:
Sub component
#Directive({
selector: '[dir]'
})
export class DirDirective {
constructor(el: ElementRef) {
// el.nativeElement corresponds to the DOM element
// the directive applies on
el.nativeElement.style.backgroundColor = 'yellow';
}
}
Container component:
#Component({
selector: 'comp',
template: `
<div dir>Some text</div>
`,
directives: [ DirDirective ]
})
export class ContainerComponent {
}
The directives attribute
To tell a bit more about the directives attribute. If the component / directive isn't a platform one, you need to explicitly define into this directive. If not, the component / directive won't apply.
This attribute can accept several values since it's an array:
#Component({
selector: 'comp',
template: `
<div>
<sub></sub>
<another></another>
</div>
`,
directives: [ SubComponent, AnotherComponent ]
})
export class ContainerComponent {
}

Categories

Resources