sdsd
sdsd

Reputation: 517

Is there some way with which i can apply css dynamically to my child component?

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?

Upvotes: 0

Views: 1722

Answers (3)

Eliseo
Eliseo

Reputation: 57939

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

Upvotes: 1

Mikkel Christensen
Mikkel Christensen

Reputation: 2797

Styling Child Components depending on Parent Component

There are two approaches I commonly take in this scenario

  1. :ng-deep - create a style rule based on a class which is set in your parent component
  2. 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

  1. Give your parent component an input and update a class on a block-element wrapping your <ng-content>.
  2. 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

  1. Add a @ContentChildren() decorator to your parent component which selects for your child components.
  2. inject a ChangeDetectorRef
  3. implement ngAfterViewInit to loop through each child and set the value
  4. call detectChanges() once done.
  5. 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

Upvotes: 1

Bertramp
Bertramp

Reputation: 385

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

Upvotes: 0

Related Questions