Angular
Preferences changes in
@suprsend/web-sdk
version >=2.0.0.In
@suprsend/[email protected]
we have revamped SDK. The whole structure of preferences have been changed from SDK side. Please upgrade to newer versions. Old version of documentation for preference is available here.
This documentation is sample example implementation of @suprsend/web-sdk in Angular applications.
Github Repository for this example implementation can be found here: https://github.com/suprsend/angular-example
Pre-requisites
- Integrate Javascript SDK
- Identify user on login and reset on logout to ensure that preference changes are tagged to the correct user
- Configure notification categories on SuprSend dashboard
- Understanding of the preference data structure and methods
Integration
There will be 3 components in example implementation:
- preference
- channel-level-preferences
- category-level-preferences
Preference component setup
preference will be the main component and inside it, there will be a property (preferencesData) that contains full user preference data. Whenever there's an update we will update the latest preference data in the preferencesData so that component to rerender. We already created SuprSend service to access SuprSend client everywhere in the application which is used to call methods.
Call getPreferences method to get the preferences data for the already identified user. This method is used to get full user preferences data from the SuprSend. This method should be called first before any update methods. Calling this method will make an API call and returns a preference response, which you can store in your instance property preferenceData if there is no error.
After that we configure 2 event listeners preferences_updated
and preferences_error
to listen to updates in preference data and errors.
Our Main component preference has 2 child components one for Category-level preference section and other for Overall Channel-level preference section.
import { SuprsendService } from '../suprsend.service';
@Component({...})
export class PreferenceComponent implements OnInit {
preferencesData: any = null;
constructor(private router: Router, private ssService: SuprsendService) {}
ngOnInit(): void {
// before getting preferences make sure to call identify method
this.ssService.ssClient.user.preferences.getPreferences().then((resp) => {
if (resp.status === 'error') {
console.log(resp.error);
} else {
this.preferencesData = resp.body;
}
});
// listen for update in preferences data
this.ssService.ssClient.emitter.on(
'preferences_updated',
(preferenceData) => { this.preferencesData = { ...preferenceData.body } } );
// listen for errors
this.ssService.ssClient.emitter.on('preferences_error', (error) => {
console.log('ERROR:', error);
});
}
}
<p *ngIf="!this.preferencesData">Loading...</p>
<div *ngIf="this.preferencesData" class="main-div">
<h3 class="main-header">Notification Preferences</h3>
<!-- notification category level preferences -->
<app-category-level-preferences [preferencesData]="this.preferencesData"></app-category-level-preferences>
<!-- overall channel level preferences -->
<app-channel-level-preferences [preferencesData]="this.preferencesData"></app-channel-level-preferences>
</div>
.main-div {
margin: 24px;
}
.main-header {
margin-bottom: 24px;
}
p,
h3 {
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
}
Category level Preference section
In category-level preferences, you'll have to fetch the data from 3 parts:
- Section - to show sections like "Product Updates" in below example
- Category - to show categories and their overall status like "Refunds" in below example
- CategoryChannel - to show communication channels inside the category and their status
Below are the steps to render category preference UI:
- Loop through the property preferenceData.sections for showing sections, show sub-categories inside each section, and show subcategory's channels inside each sub-category.
- Add a switch button next to each sub-category for opting in and out of the category. Add checkbox components in sub-category channels for opting in and out of category-channel. You can use any third-party npm package to import these components or design your own component.
- To update category preference on the click of the switch button, call updateCategoryPreference method and if no error is received in response, update the latest data in the instance property. For preference state
opt-in
set the switch state as on and off for theopt-out
state.
- To update category-channel preference on the click of checkbox next to each channel, call the updateChannelPreferenceInCategory method. Update the latest data in the instance property if no error is received in response. For preference state
opt-in
set the checkbox state as checked and unchecked for theopt-out
state.
import { Component, Input } from '@angular/core';
import { PreferenceOptions } from '@suprsend/web-sdk';
import { SuprsendService } from '../suprsend.service';
@Component({
selector: 'app-category-level-preferences',
templateUrl: './category-level-preferences.component.html',
styleUrls: ['./category-level-preferences.component.css'],
})
export class CategoryLevelPreferencesComponent {
@Input() public preferencesData: any;
constructor(private ssService: SuprsendService) {}
async handleCategoryPreferenceChange(e: boolean, subcategory: string) {
const resp =
await this.ssService.ssClient.user.preferences.updateCategoryPreference(
subcategory,
e ? PreferenceOptions.OPT_IN : PreferenceOptions.OPT_OUT
);
if (resp.error) {
console.log(resp.error);
} else {
this.preferencesData = { ...resp.body };
}
}
async handleChannelPreferenceInCategoryChange(
channel: any,
category: string
) {
if (!channel.is_editable) return;
const resp =
await this.ssService.ssClient.user.preferences.updateChannelPreferenceInCategory(
channel.channel,
channel.preference === PreferenceOptions.OPT_IN
? PreferenceOptions.OPT_OUT
: PreferenceOptions.OPT_IN,
category
);
if (resp.status === 'error') {
console.log(resp.error);
} else {
this.preferencesData = { ...resp.body };
}
}
}
<div
*ngFor="let section of this.preferencesData.sections"
class="cat-container"
>
<div *ngIf="section.name">
<div class="section-name-container">
<p class="section-name-text">{{ section.name }}</p>
<p class="section-description-text">{{ section.description }}</p>
</div>
</div>
<div *ngFor="let subcategory of section.subcategories">
<div class="subcategory-container">
<div class="subcategory-top-div">
<div>
<p class="subcategory-name">{{ subcategory.name }}</p>
<p class="subcategory-description">{{ subcategory.description }}</p>
</div>
<ui-switch
size="small"
color="#2463eb"
[disabled]="!subcategory.is_editable"
[checked]="subcategory.preference === 'opt_in'"
(change)="
handleCategoryPreferenceChange($event, subcategory.category)
"
></ui-switch>
</div>
<div class="subcategory-channel-container">
<div
*ngFor="let channel of subcategory.channels"
class="category-channel-checkbox"
>
<input
type="checkbox"
[id]="subcategory.category + '-' + channel.channel"
[disabled]="!channel.is_editable"
[checked]="channel.preference === 'opt_in'"
(change)="
handleChannelPreferenceInCategoryChange(
channel,
subcategory.category
)
"
/>
<label
class="category-channel-label"
[for]="subcategory.category + '-' + channel.channel"
>{{ channel.channel }}</label
>
</div>
</div>
</div>
</div>
</div>
.cat-container {
margin-bottom: 24px;
}
.section-name-container {
background-color: #fafbfb;
padding-top: 12px;
padding-bottom: 12px;
margin-bottom: 18px;
}
.section-name-text {
font-size: 18px;
font-weight: 500;
color: #3d3d3d;
}
.section-description-text {
color: #6c727f;
}
p,
label {
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
}
.subcategory-channel-container {
display: flex;
gap: 10;
margin-top: 12px;
}
.subcategory-name {
font-size: 16px;
font-weight: 600;
color: #3d3d3d;
}
.subcategory-top-div {
display: flex;
justify-content: space-between;
align-items: center;
}
.subcategory-container {
border-bottom: 1px solid #d9d9d9;
padding-bottom: 12px;
margin-top: 18px;
}
.subcategory-description {
color: #6c727f;
font-size: 14px;
margin-top: 6px;
}
.category-channel-checkbox {
margin-right: 20px;
}
.category-channel-label {
margin-left: 4px;
cursor: pointer;
}
Channel level Preference section
Below are the steps to render channel preference UI:
- Loop through the property preferenceData.channel_preferences for showing channels and for every channel item we will show an option to select preference using radio buttons.
- Add a radio button next, against channel level options for switching from
all
torequired
preference in channel. - To update channel preference on the click of the radio button, call updateOverallChannelPreference method, and if no error is received in response, update the latest data in the state.
import { Component, Input } from '@angular/core';
import { ChannelLevelPreferenceOptions } from '@suprsend/web-sdk';
import { SuprsendService } from '../suprsend.service';
@Component({
selector: 'app-channel-level-preferences',
templateUrl: './channel-level-preferences.component.html',
styleUrls: ['./channel-level-preferences.component.css'],
})
export class ChannelLevelPreferencesComponent {
@Input() public preferencesData: any;
constructor(private ssService: SuprsendService) {}
async handleChange(channel: string, preference: string) {
const preferenceStatus =
preference === 'ALL'
? ChannelLevelPreferenceOptions.ALL
: ChannelLevelPreferenceOptions.REQUIRED;
const resp =
await this.ssService.ssClient.user.preferences.updateOverallChannelPreference(
channel,
preferenceStatus
);
if (resp.status === 'error') {
console.log(resp.error);
} else {
this.preferencesData = { ...resp.body };
}
}
}
<div>
<div class="channel-header-div">
<p class="channel-header-p">What notifications to allow for channel?</p>
</div>
<div>
<div *ngFor="let channel of this.preferencesData.channel_preferences">
<div class="channel-container">
<p class="channel-channel-text">{{ channel.channel }}</p>
<p
class="channel-help-text"
*ngIf="channel.is_restricted; else allText"
>
Allow required notifications only
</p>
<ng-template #allText
><p class="channel-help-text">Allow all notifications</p>
</ng-template>
<div class="channel-radio-container">
<p class="channel-radio-pref-text">
{{ channel.channel }} Preferences
</p>
<div class="radio-grp">
<div class="radio-grp-2">
<div class="radio-grp-container">
<div>
<input
type="radio"
[checked]="!channel.is_restricted"
name="all-{{ channel.channel }}"
id="all-{{ channel.channel }}"
(change)="handleChange(channel.channel, 'ALL')"
/>
</div>
<label class="all-label" for="all-{{ channel.channel }}">
All
</label>
</div>
<p class="channel-radiohelp-text">
Allow All Notifications, except the ones that I have turned off
</p>
</div>
<div>
<div class="radio-grp-container">
<div>
<input
type="radio"
name="required-{{ channel.channel }}"
id="required-{{ channel.channel }}"
[checked]="channel.is_restricted"
(change)="handleChange(channel.channel, 'REQUIRED')"
/>
</div>
<label
class="required-label"
for="required-{{ channel.channel }}"
>
Required
</label>
</div>
<p class="channel-radiohelp-text">
Allow only important notifications related to account and
security settings
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
.channel-header-div {
background-color: #fafbfb;
padding-top: 12px;
padding-bottom: 12px;
margin-bottom: 18px;
}
.channel-header-p {
font-size: 18px;
font-weight: 500;
color: #3d3d3d;
}
p,
label {
margin: 0;
padding: 0;
font-family: Arial, Helvetica, sans-serif;
}
.channel-container {
border: 1px solid #d9d9d9;
border-radius: 5px;
padding: 12px 24px;
margin-bottom: 24px;
}
.channel-channel-text {
font-size: 18px;
font-weight: 500;
color: #3d3d3d;
}
.channel-help-text {
color: #6c727f;
font-size: 14px;
margin-top: 6px;
}
.channel-radio-container {
margin-top: 12px;
margin-left: 24px;
}
.channel-radio-pref-text {
color: #3d3d3d;
font-size: 16px;
font-weight: 500;
margin-top: 18px;
border-bottom: 1px solid #e8e8e8;
}
.channel-radiohelp-text {
color: #6c727f;
font-size: 14px;
margin-left: 32px;
margin-top: 4px;
}
.label-required {
margin-left: 12px;
}
.radio-grp {
margin-top: 12px;
}
.radio-grp-2 {
margin-bottom: 8px;
}
.radio-grp-container {
display: flex;
align-items: center;
}
.all-label {
margin-left: 12px;
cursor: pointer;
}
.required-label {
margin-left: 12px;
cursor: pointer;
}
Updated 13 days ago