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
Applying Custom Theme
Answers approved by surveyjs Support
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 thanSurveyModel
.
JavaScriptimport { 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 theSurveyModel
'sapplyTheme
method to apply a custom theme and also calls therender()
function to force UI updates.
JavaScriptimport {
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 aSurveyComponent
.
JavaScriptimport { 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
@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.
Hi Tina,
Thank you for sharing a demo. I'll research it and update you shortly.