- ABP Framework version: v4.3.1
- UI type: Angular
- DB provider: EF Core
- Tiered (MVC) or Identity Server Separated (Angular): no / no
- Exception message and stack trace:
- Steps to reproduce the issue:
Hello. I am trying to add boolean field to Personal Setting (Personal info) in Angular UI. Field already exists in AbpUsers table.
I am following this guide https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement.
First I installed module @abp/ng.account with yarn so I can get eAccountComponents.PersonalSettings for replacing. After I installed module I got bunch of errors in others components (only html classes)...
After that I created my custom component and added replaceble logic to AppComponent
import { Component } from '@angular/core';
import { CustomPersonalSettingsComponentComponent } from './custom-personal-settings-component/custom-personal-settings-component.component';
import { ReplaceableComponentsService } from '@abp/ng.core';
import { eAccountComponents } from '@abp/ng.account';
@Component({
selector: 'app-root',
template: `
<abp-loader-bar></abp-loader-bar>
<abp-dynamic-layout></abp-dynamic-layout>
`,
})
export class AppComponent {
constructor(
private replaceableComponents: ReplaceableComponentsService, // injected the service
) {
this.replaceableComponents.add({
component: CustomPersonalSettingsComponentComponent,
key: eAccountComponents.PersonalSettings,
});
}
}
After adding imports, I got error that abp-dynamics-layout is not a known element...
Even with all that errors, my project compiled successfully and I can use all my broken components (html). When I go to Personal info, it still show default component, not mine...
Anyone know why I got these errors in html after installing @abp/ng.account and why PersonalInfo replacing is not working? If I try and replace Manage profile component it works...
13 Answer(s)
-
0
I fixed problems with html components.
First I cleared cache, then deleted node_modules folder and reinstalled all modules with yarn install... Now I dont have any errors with html, but replacing PageSettings component still not working...
-
0
Any answer to this?
-
0
Hi,
Please follow the steps below to replace personal settings component:
- Run the following command to generate account module:
yarn ng generate module account
- Run command below to generate a component called
CustomPersonalSettings
:
yarn ng generate component account/custom-personal-settings --skip-tests --inline-style
- Replace the generated
src/account/account.module.ts
content with following:
import { NgModule } from '@angular/core'; import { AccountPublicModule } from '@volo/abp.ng.account/public'; import { SharedModule } from '../shared/shared.module'; import { CustomPersonalSettingsComponent } from './custom-personal-settings/custom-personal-settings.component'; @NgModule({ declarations: [CustomPersonalSettingsComponent], imports: [AccountPublicModule.forChild({}), SharedModule], }) export class AccountModule {}
- Replace the generated
src/account/custom-personal-settings/custom-personal-settings.component.ts
content with following:
import { ConfigStateService, ProfileService, SubscriptionService } from '@abp/ng.core'; import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared'; import { Component, EmbeddedViewRef, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AccountService, ManageProfileStateService, ProfileResponse, } from '@volo/abp.ng.account/public'; import { Observable } from 'rxjs'; import { filter, tap } from 'rxjs/operators'; @Component({ selector: 'app-custom-personal-settings', templateUrl: './custom-personal-settings.component.html', styles: [], }) export class CustomPersonalSettingsComponent implements OnInit { storedProfile: ProfileResponse; profile$: Observable<ProfileResponse> = this.manageProfileState.getProfile$(); modalVisible = false; modalBusy: boolean = false; modalRef: EmbeddedViewRef<any>; form: FormGroup; token: string = ''; isEmailUpdateEnabled: boolean = true; isUserNameUpdateEnabled: boolean = true; showEmailVerificationBtn$ = this.manageProfileState.createStateStream( state => !state.hideEmailVerificationBtn ); get userId(): string { return this.configState.getDeep('currentUser.id'); } askForImmediatePhoneNumberVerification = () => { this.confirmationService .info('AbpAccount::DoYouWantToVerifyPhoneNumberMessage', 'AbpAccount::ConfirmYourPhoneNumber') .pipe(filter(status => status === Confirmation.Status.confirm)) .subscribe(this.initPhoneNumberConfirmation); }; checkPhoneNumberChanges = ({ phoneNumber }: ProfileResponse) => { if (phoneNumber && phoneNumber !== this.storedProfile?.phoneNumber) { this.askForImmediatePhoneNumberVerification(); } }; buildForm = (profile: ProfileResponse) => { this.form = this.fb.group({ userName: [profile.userName, [Validators.required, Validators.maxLength(256)]], email: [profile.email, [Validators.required, Validators.email, Validators.maxLength(256)]], name: [profile.name || '', [Validators.maxLength(64)]], surname: [profile.surname || '', [Validators.maxLength(64)]], phoneNumber: [profile.phoneNumber || '', [Validators.maxLength(16)]], }); }; initPhoneNumberConfirmation = () => { this.accountService .sendPhoneNumberConfirmationToken({ phoneNumber: this.form.value.phoneNumber, userId: this.userId, }) .pipe(tap(() => (this.token = ''))) .subscribe(this.openModal); }; openModal = () => { this.modalVisible = true; }; removeModal = () => { this.modalVisible = false; }; setPhoneNumberAsConfirmed = () => { this.storedProfile.phoneNumberConfirmed = true; }; constructor( private fb: FormBuilder, private accountService: AccountService, private toasterService: ToasterService, private confirmationService: ConfirmationService, private configState: ConfigStateService, private subscription: SubscriptionService, private manageProfileState: ManageProfileStateService, private profileService: ProfileService ) {} confirmPhoneNumber() { this.accountService .confirmPhoneNumber({ token: this.token, userId: this.userId }) .pipe(tap(this.setPhoneNumberAsConfirmed), tap(this.removeModal)) .subscribe(() => { this.toasterService.success('AbpAccount::Verified', '', { life: 5000 }); }); } sendEmailVerificationToken() { this.accountService .sendEmailConfirmationToken({ appName: 'Angular', email: this.form.value.email, userId: this.userId, }) .subscribe(() => { this.toasterService.success('AbpAccount::EmailConfirmationSentMessage', '', { messageLocalizationParams: [this.form.value.email], }); this.manageProfileState.setHideEmailVerificationBtn(true); }); } ngOnDestroy() { this.removeModal(); } ngOnInit() { this.subscription.addOne( this.profile$.pipe( filter<ProfileResponse>(Boolean), tap(profile => (this.storedProfile = profile)), tap(this.checkPhoneNumberChanges) ), this.buildForm ); const settings = this.configState.getSettings(); this.isEmailUpdateEnabled = (settings['Abp.Identity.User.IsEmailUpdateEnabled'] || '').toLowerCase() !== 'false'; this.isUserNameUpdateEnabled = (settings['Abp.Identity.User.IsUserNameUpdateEnabled'] || '').toLowerCase() !== 'false'; } submit() { if (this.form.invalid) return; const { phoneNumberConfirmed, ...profile } = this.form.value; this.profileService.update(profile).subscribe(res => { this.manageProfileState.setProfile(res); this.toasterService.success('AbpAccount::PersonalSettingsSaved', '', { life: 5000 }); }); } }
- Replace the generated
src/account/custom-personal-settings/custom-personal-settings.component.html
content with following:
<form validateOnSubmit *ngIf="form" [formGroup]="form" (ngSubmit)="submit()"> <div class="form-group"> <label for="username">{{ 'AbpIdentity::DisplayName:UserName' | abpLocalization }}</label ><span> * </span ><input type="text" id="username" class="form-control" formControlName="userName" autofocus (keydown.space)="$event.preventDefault()" [readonly]="!isUserNameUpdateEnabled" /> </div> <div class="row"> <div class="col col-md-6"> <div class="form-group"> <label for="name">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</label ><input type="text" id="name" class="form-control" formControlName="name" /> </div> </div> <div class="col col-md-6"> <div class="form-group"> <label for="surname">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</label ><input type="text" id="surname" class="form-control" formControlName="surname" /> </div> </div> </div> <div class="form-group"> <label for="email-address">{{ 'AbpIdentity::DisplayName:Email' | abpLocalization }}</label ><span> * </span> <div class="input-group" validationTarget validationStyle> <input type="text" id="email-address" class="form-control" formControlName="email" [readonly]="!isEmailUpdateEnabled" /> <div *ngIf="{ edited: form.value.email !== storedProfile?.email, confirmed: storedProfile?.emailConfirmed } as data" class="input-group-append" > <abp-personal-settings-verify-button *ngIf="(showEmailVerificationBtn$ | async) || data.edited || data.confirmed" [verified]="data.confirmed" [edited]="data.edited" (btnClick)="sendEmailVerificationToken()" ></abp-personal-settings-verify-button> </div> </div> </div> <div class="form-group"> <label for="phone-number">{{ 'AbpIdentity::DisplayName:PhoneNumber' | abpLocalization }}</label> <div class="input-group mb-3"> <input type="text" id="phone-number" class="form-control" formControlName="phoneNumber" /> <div class="input-group-append" *ngIf="storedProfile?.phoneNumber"> <abp-personal-settings-verify-button [verified]="storedProfile?.phoneNumberConfirmed" [edited]="form.value.phoneNumber !== storedProfile?.phoneNumber" (btnClick)="initPhoneNumberConfirmation()" ></abp-personal-settings-verify-button> </div> </div> </div> <abp-button iconClass="fa fa-check" buttonClass="btn btn-primary color-white" buttonType="submit" [disabled]="form?.invalid" > {{ 'AbpIdentity::Save' | abpLocalization }}</abp-button > </form> <abp-modal [(visible)]="modalVisible" [busy]="modalBusy" size="sm"> <ng-template #abpHeader> <h5>{{ 'AbpAccount::Verify' | abpLocalization }}</h5> </ng-template> <ng-template #abpBody> <form (ngSubmit)="confirmPhoneNumber()"> <div class="mt-2"> <p> {{ 'AbpAccount::ConfirmationTokenSentMessage' | abpLocalization: form.value.phoneNumber }} </p> <div class="form-group"> <label for="confirm-phone-number">{{ 'AbpAccount::PhoneConfirmationToken' | abpLocalization }}</label> <input [(ngModel)]="token" id="confirm-phone-number" name="confirm-phone-number" class="form-control" type="text" autofocus /> </div> </div> </form> </ng-template> <ng-template #abpFooter> <button abpClose type="button" class="btn btn-secondary"> {{ 'AbpAccount::Cancel' | abpLocalization }} </button> <abp-button type="abp-button" iconClass="fa fa-check" (click)="confirmPhoneNumber()"> {{ 'AbpAccount::Save' | abpLocalization }} </abp-button> </ng-template> </abp-modal>
- Open
src/app-routing.module.ts
file modifyaccount
route like this:
{ path: 'account', loadChildren: () => import('./account/account.module').then(m => m.AccountModule), },
- Open
src/app.component.ts
and replace thePersonalSettingsComponent
as shown below:
import { Component, OnInit } from '@angular/core'; import { eAccountManageProfileTabNames, ManageProfileTabsService, } from '@volo/abp.ng.account/public/config'; import { CustomPersonalSettingsComponent } from './account/custom-personal-settings/custom-personal-settings.component'; @Component({ selector: 'app-root', template: ` <abp-loader-bar></abp-loader-bar> <abp-dynamic-layout></abp-dynamic-layout> `, }) export class AppComponent implements OnInit { constructor( private manageProfileTabs: ManageProfileTabsService ) {} ngOnInit() { this.manageProfileTabs.patch(eAccountManageProfileTabNames.PersonalInfo, { component: CustomPersonalSettingsComponent, }); } }
You can replace a tab in the my profile page by using the
ManageProfileTabsService
.I hope this helps you.
-
0
Hello.
Thanks, it works. Is there any guide on how to add aditional field to Profile.Response or how to extend Profile class and module to add new field so user can update it?
-
0
I am following this guide here: https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Overriding-Services#extending-data-transfer-objects but I don't have class YourProjectNameDtoExtensions in my solution.
I created application with abp suite version 4.3.0.
-
0
hi
You can create a
MyProjectNameDtoExtensions
file in yourApplication.Contracts
layer.https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameDtoExtensions.cs
I will add this file to next version of app pro template.
-
0
Hi, thanks for answer.
I created file but I can not extend ProfileDto and UpdateProfileDto. What am I missing? I have boolean field EmailNotifications, which I added to AppUser class and to MyProjectExtensionConfigurator. It shows on Administrations/Identity Managament/Users and also it works when I edit it there. Now I want to add this field to Personal info, so user can check/uncheck it himself. I followed your guide but can not add my custom property to ProfileDto. I found this line of code which populated ExtraProperties of ProfileDto but not also UpdateProfileDto. What am I doing wrong?
Thanks for your answers!
-
0
hi beuko
Can you share your full code about extending-data-transfer-objects?
-
0
AppUser.cs
//added this line public bool EmailNotifications { get; protected set; }
Application.Contracts/SKAPDtoExtensions.cs
public static class SKAPDtoExtensions { private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); public static void Configure() { OneTimeRunner.Run(() => { /* You can add extension properties to DTOs * defined in the depended modules. * * Example: * * ObjectExtensionManager.Instance * .AddOrUpdateProperty<IdentityRoleDto, string>("Title"); * * See the documentation for more: * https://docs.abp.io/en/abp/latest/Object-Extensions */ ObjectExtensionManager.Instance .AddOrUpdateProperty<bool>( new[] { typeof(ProfileDto), typeof(UpdateProfileDto) }, "EmailNotifications" ); }); } }
Domain.Shared/SKAPModuleExtensionConfigurator.cs
public static class SKAPModuleExtensionConfigurator { private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner(); public static void Configure() { OneTimeRunner.Run(() => { ConfigureExistingProperties(); ConfigureExtraProperties(); }); } private static void ConfigureExtraProperties() { /* You can configure extra properties for the * entities defined in the modules used by your application. * * This class can be used to define these extra properties * with a high level, easy to use API. * * Example: Add a new property to the user entity of the identity module ObjectExtensionManager.Instance.Modules() .ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty<string>( //property type: string "SocialSecurityNumber", //property name property => { //validation rules property.Attributes.Add(new RequiredAttribute()); property.Attributes.Add(new StringLengthAttribute(64) {MinimumLength = 4}); //...other configurations for this property } ); }); }); * See the documentation for more: * https://docs.abp.io/en/abp/latest/Module-Entity-Extensions */ ObjectExtensionManager.Instance.Modules() .ConfigureIdentity(identity => { identity.ConfigureUser(user => { user.AddOrUpdateProperty<bool>( "EmailNotifications", options => { options.DefaultValue = false; } ); }); }); } }
-
0
Hi,
The profile UI does not support object extension, you can customize it via https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement
-
0
Hello liangshiwei,
I already customized angular UI to show this field, now I need to get value from database (via DTO extensions) and also save it back to database.
-
0
-
0
This question has been automatically marked as stale because it has not had recent activity.