Question T19437
Visible to All Users

Applying Custom Theme

created 5 months ago

Hello!
I've been working on some scenarios using the SurveyJS Form Library. I thought it might be easier to create custom themes if I can actively change and view the changes made to different aspects in the survey. I used an onChange event to view the new colors applied, but I am facing an issue and I am unsure if this is due to my code or not. I have created an app in angular with a preview of the survey, as well as a panel which displays color inputs that would change the color of the property I would want changed. I save the new theme in a variable and apply it to the survey using the applyTheme method. The first change in color(regardless of which property I change) is applied to the survey, however when I try to change any other properties after that, the change does not get applied. I am unsure how to fix this issue or what is causing it, and would really appreciate any advice or help you could give me in regards to this.
I have the practice in a codeSandbox: survey-theme-practice
I am looking forward to hearing from you!
Thank you and kind regards,
Tina

Comments (1)

    Hi Tina,
    Thank you for sharing a demo. I'll research it and update you shortly.

    Answers approved by surveyjs Support

    created 5 months ago (modified 5 months ago)

    Hi Tina,
    I researched your demo.

    Although the this.customizedTheme object contains a valid and updated theme object, the theme is not immediately reflected in UI unless you force a component update, for instance, by resizing a window.

    The issue is caused by the fact that Angular does not automatically detect changes in object properties that are deeply nested or that Angular hasn't been explicitly notified about. When you update the theme in your customizedTheme object and call survey.applyTheme, Angular does not automatically trigger a change detection cycle to update the UI, which is why the changes only appear after some other action like resizing the window.

    In SurveyJS Form Library for Angular (the survey component), we actually detach a default changeDetectorRef and call the this.changeDetectorRef.detectChanges(); function only when a model is changed. This is usually done at the application startup. If you wish to update a survey and apply custom CSS color scheme, you can call the SurveyModel's render function. It will invoke the SurveyModel's renderCallback which will invoke the this.detectChanges() function of a survey content element.

    I updated your code as follows: View CodeSandbox.

    • In app.component.ts, I pass a reference to SurveyComponent rather than SurveyModel.
    JavaScript
    import { Component, ChangeDetectorRef } from '@angular/core'; import { SurveyComponent } from './survey/survey.component'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], }) export class AppComponent { title = 'CodeSandbox'; surveyComponent!: SurveyComponent; constructor(private cdr: ChangeDetectorRef) {} getSurvey(value: SurveyComponent) { this.surveyComponent = value; } }
    HTML
    <div class="main"> <app-survey (surveyEvent)="getSurvey($event)"></app-survey> <app-survey-edit [survey]="surveyComponent"></app-survey-edit> </div>
    • In survey.component.ts, I declared a custom applyCustomTheme function. This function calls the SurveyModel's applyTheme method to apply a custom theme and also calls the render() function to force UI updates.
    JavaScript
    import { Component, OnInit, Output, EventEmitter, ChangeDetectorRef, } from '@angular/core'; import { Model } from 'survey-core'; import { SurveyModule } from 'survey-angular-ui'; @Component({ selector: 'app-survey', standalone: true, imports: [SurveyModule], templateUrl: './survey.component.html', styleUrls: ['./survey.component.css'], }) export class SurveyComponent implements OnInit { @Output() surveyEvent = new EventEmitter<any>(); surveyJson = { elements: [ { type: 'text', title: 'Full Name', }, { type: 'text', inputType: 'date', title: 'Date of Birth', }, { type: 'boolean', title: 'Do you have a drivers license?', }, ], }; surveyModel!: Model; survey = new Model(this.surveyJson); ngOnInit(): void { this.surveyModel = this.survey; this.surveyEvent.emit(this); } applyCustomTheme(theme: any) { this.survey.applyTheme(theme); this.survey.render(); } }
    • In survey-edit.component.ts, I call the applyCustomTheme of a SurveyComponent.
    JavaScript
    import { Component, Input } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { SurveyComponent } from '../survey/survey.component'; @Component({ selector: 'app-survey-edit', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './survey-edit.component.html', styleUrls: ['./survey-edit.component.css'], }) export class SurveyEditComponent { @Input() survey: SurveyComponent | null = null; defaultTheme: any; cssColorVariables = [ { variableName: '--sjs-general-backcolor-dim', label: 'Background Color' }, { variableName: '--sjs-general-backcolor', label: 'Panel Color' }, { variableName: '--sjs-general-forecolor', label: 'Text Color' }, ]; cssColorValue: { [key: string]: any } = {}; customizedTheme: any; constructor(private http: HttpClient) {} ngOnInit() { this.getDefaultTheme(); } getDefaultTheme() { this.http.get('assets/DefaultLight.json').subscribe((response) => { this.defaultTheme = response; this.getCssVariables(this.defaultTheme); }); } getCssVariables(theme: any) { const themeCss = theme.cssVariables; this.cssColorVariables.forEach((variable) => { this.cssColorValue[variable.variableName] = this.isHex( themeCss[variable.variableName] ) ? themeCss[variable.variableName] : this.rgbaToHexA(themeCss[variable.variableName]); }); } isHex(color: string): boolean { return /^#([0-9A-F]{3}){1,2}$/i.test(color); } rgbaToHexA(rgba: string) { const parts = rgba .substring(rgba.indexOf('(') + 1, rgba.lastIndexOf(')')) .split(/,\s*/); const r = parseInt(parts[0]); const g = parseInt(parts[1]); const b = parseInt(parts[2]); const a = parseInt(parts[3]) * 255; const toHex = (value: number) => value.toString(16).padStart(2, '0').toUpperCase(); return `#${toHex(r)}${toHex(g)}${toHex(b)}`; } onColorChangeEvent(variableName: string, newValue: string) { if (!this.survey) { console.error('Survey object is not defined.'); return; } if (this.customizedTheme == null) { const chosenTheme = this.defaultTheme; chosenTheme['cssVariables'][variableName] = newValue; this.customizedTheme = chosenTheme; } else { this.customizedTheme['cssVariables'][variableName] = newValue; } if (this.survey) { this.survey.applyCustomTheme(this.customizedTheme); } } }

    I hope the updated demo helps. Please let me know if you have further questions.

    P.S. I would appreciate it if you could leave a short review on SurveyJS page at Capterra. As we’re building our business, every review helps build credibility for future customers and your feedback would be greatly appreciated.

    Thank you in advance

      Comments (1)

        @Tina, please also take note that we offer a Theme Editor out of the box: View Demo. It is available as a part of a commercial SurveyJS Creator component.

        Should you have any further questions, let me know.