I have a couple of buttons that I want to display only if user is logged in. My problem is that simple *ngIf is not working like I want it to.
For example I have this button Add Recipe:
<div class="background">
<button type="button" *ngIf="isUserAuthenticated" class="btn btn-primary float-right m-2" data-bs-toggle="modal" data-bs-target="#exampleModal"
(click)="addClick()"
data-backdrop="static"
data-keyboard="false">
Add Coffee
</button>
It is button for showing modal. I want that button to be displayed only if user is authenticated. This is how my .ts looks like:
export class RecipeDetailsComponent implements OnInit {
...
public isUserAuthenticated: boolean = false;
ngOnInit(): void {
this.userService.authChanged
.subscribe(res => {
this.isUserAuthenticated = res;
})
}
and my userService methods for checking if authenticated:
export class UserService {
private _authChangeSub = new Subject<boolean>()
public authChanged = this._authChangeSub.asObservable();
public sendAuthStateChangeNotification = (isAuthenticated: boolean) => {
this._authChangeSub.next(isAuthenticated);
}
public isUserAuthenticated = (): boolean => {
const token = localStorage.getItem("token");
return token != null && this._jwtHelper.isTokenExpired(token) == false;
}
Button appears only If i set isUserAuthenticated = true when declaring and hides if it false.
I guess button reads isUserAthenticated property and show it selfs before ngOnInit.
How can I delay button display till ngOnInit is resolved or just refresh after that?
If I understand your question, You're having trouble because of timing. Your code doesn't include when or how the value changes but I am guessing that the value is changed before your component initializes and therefore you are subscribing late?
If so, I recommend changing
private _authChangeSub = new Subject<boolean>()
to
private _authChangeSub = new ReplaySubject<boolean>(1);
This way, even when subscribing after the value has been emitted, the component will still receive the value. See https://www.learnrxjs.io/learn-rxjs/subjects/replaysubject
Related
I have several text boxes and a save button
Each text box value is loaded using the following approach
{
this.getElement('test3lowerrangethreshold', 'iaSampling.iaGlobalConfiguration.test3lowerrangethreshold',
enums.IASamplingGlobalParameters.ModerationTest3LowerThreshold)
}
private getElement(elementid: string, label: string, globalparameter: enums.IASamplingGlobalParameters): JSX.Element {
let globalParameterElement =
<div className='row setting-field-row' id={elementid}><
span className='label'>{localeHelper.translate(label)}</span>
<div className="input-wrapper small">
<input className='input-field' placeholder='text' value={this.globalparameterhelper.getDataCellContent(globalparameter, this.state.globalParameterData)} />
</div>
</div>;
return globalParameterElement;
}
Helper Class
class IAGlobalParametesrHelper {
public getDataCellContent = (globalparameter: enums.IASamplingGlobalParameters, configdata: Immutable.List<ConfigurationConstant>) => {
return configdata?.find(x => x.key === globalparameter)?.value;
}
}
This works fine. Now the user is allowed to update these text values.And on click of save the changes should be reflected by calling a web api .
I have added an onlick event like this
<a href='#' className='button primary default-size' onClick={this.saveGlobalParameterData}>Save</a>
Now inorder to save the data i need a way to identify the text element which has changed.For that i have added an update method within the Helper class
public updateCellValue = (globalparameter: enums.IASamplingGlobalParameters, configdata: Immutable.List<ConfigurationConstant>,updatedvalue:string) => {
let itemIndex = configdata.findIndex(x => x.key === globalparameter);
configdata[itemIndex] = updatedvalue;
return configdata;
}
and return the updated configdata ,and i plan to call this method in the onchange event of every text box like this
<input className='input-field' placeholder='text' onchange={this.setState({ globalParameterData: this.globalparameterhelper.updateCellValue(globalparameter, this.state.globalParameterData, (document.getElementById(elementid) as HTMLInputElement).value})}
But this does not seem like a correct approach as there are number of syntactical errors. I initially got the data using an actioncreator like this.Please advice.
samplingModerationActionCreator.getGlobalParameters();
samplingModerationStore.instance.addListener(samplingModerationStore.SamplingModerationStore
.IA_GLOBAL_PARAMETER_DATA_GET_EVENT,
this.getGlobalParameterData);
}
We are trying to pass data from one component to another and below is the approach we are taking. When there is no data we want to show the error message
<div *ngIf="showGlobalError">
<h6>The reporting project doesn't have any Shippable Items</h6>
</div>
and the component.ts is like
showGlobalError = true;
constructor(private psService: ProjectShipmentService, private pdComp: ProjectDetailsComponent) {
this.psService.tDate.subscribe(x => this.cachedResults = x);
}
ngOnInit() { }
ngDoCheck() {
if (this.cachedResults.length > 0 && this.count <= 1) {
this.showGlobalError = false;
this.populateArrays();
this.count++;
}
}
populateArrays() {
this.reportingProject = [this.pdComp.rProjectNumber];
this.projectSalesOrder = this.pdComp.rSalesOrder;
this.clearFilter();
........
The issue is Even though there is data in the this.cachedResults that is this.cachedResults.length not equal to '0' for few seconds 'The reporting project doesn't have any Shippable Items' is shown in the page and then shows the data I am not sure if this something with the ngDoCheck() is causing this. Any help is greatly appreciated
Since, the default value of showGlobalError is true, the page load shows the error message.
Please make it by default false and make it true when this.cachedResults.length is 0 or this.cachedResults is undefined or this.cachedResults is null.
Hope this solves your problem.
Rather than subscribing in the code you can use the async pipe in your template
items$ = this.psService.tDate;
showGlobalError$ = this.items$.pipe(map(results => !results || !results.length));
constructor(private psService: ProjectShipmentService, private pdComp: ProjectDetailsComponent) { }
and in your template
<div *ngIf="showGlobalError$ | async">
<h6>The reporting project doesn't have any Shippable Items</h6>
</div>
<ng-template *ngFor="let item of items$ | async">
Do stuff with {{item | json}}
</ng-template>
This manages your subscription for you and fixes the memory leak you have in your code with the subscription you don't unsubscribe from.
Take a look at alibrary I wrote for this sort of thing, make caching data a lot easier. https://medium.com/#adrianbrand/angular-state-management-with-rxcache-468a865fc3fb
In my service I make a call:
this.potentialOrganizations(currentNode.id)
.subscribe(data => {
console.log('consoling the org data!!!!!!! ' + JSON.stringify(data))
this.potentialOrgData = [];
this.potentialOrgData = data;
this._potentialOrgs.onNext(true);
})
The data consoles fine, as you can see, I tried using an observable method but it's not working for some reason!
I may need a new way to be able to call the data, as I said in my components html I have this: although it doesn't work:
<ul *ngIf="this.engagementService.potentialOrgData.length > 0">
<li *ngFor="let org of this.engagementService.potentialOrgData">
<p class="listedOrgs">{{ org.name }}</p>
</li>
</ul>
In my component I had this:
ngOnInit(): void {
this.engagementService.potentialOrgs$.subscribe(
(data) => {
if (data) {
console.log('are we hitting here inside the potentialORG DATA!??!?!?!!?!?')
this.potentialOrganizations = this.engagementService.potentialOrgData;
}
}
)
this.potentialOrganizations = this.engagementService.potentialOrgData;
}
it doesnt console, even though in my service i have the observable thing set:
private _potentialOrgs = new BehaviorSubject<boolean>(false);
public potentialOrgs$ = this._potentialOrgs.asObservable();
I was thinking maybe I need to use #input instead? but how to do that properly?
You could make this a little more simple here by trying the following. If potentialOrgData is set from a subscription in the service which it is, it will stay fresh as subscriptions stay open. You will be able to use the variable in the service directly.
public requestedData = [];
public ngOnInit(): void {
this.requestedData = this.engagementService.potentialOrgData;
}
In your template.
<ul *ngIf="requestedData.length">
<li *ngFor="let org of requestedData">
<p class="listedOrgs">{{ org.name }}</p>
</li>
</ul>
Behaviour Subjects and Observable's are very powerful but not always necessary.
My component's html is this:
<div id="summary">
<div *ngFor="let question of thisSurvey">
<div>
<span class="badge">#{{question.questionNumber}}</span>
<span>{{question.questionText}}</span>
</div>
<p>Your answer: {{question.questionAnswer}}</p>
</div>
</div>
<br/>
<button class="btn btn-danger yes-no-btn" routerLink="/survey">Go Back</button>
<button class="btn btn-primary" [routerLink]="submitSurvey()" routerLinkActive="active">Finish</button> <!-- Issue here -->
When the page loads, submitSurvey is invoked immediately, and is then constantly invoked. This is submitSurvey:
// Send the answers back to the api for processing
submitSurvey() {
// Make sure everything is answered
const allOKClientSide: boolean = this.surveyService.checkEntireForm(this.thisSurvey);
if (allOKClientSide) {
if (this.surveyService.checkFormOnline(this.thisSurvey).subscribe()) {
return '/placeOne';
}
}
return '/placeTwo';
}
The method begins to hit the service immediately and continues until I kill the server. How do I keep the function from being invoked until the button is clicked? I'm new to Angular and am probably just making a rookie mistake, if so you may point that out as well. Thanks in advance.
[routerLink] is an Input, note the []. So Angular will resolve that immediately and every change detection cycle, to satisfy the template. You want to use (click) which is an Output, note the () and will only call when the button is clicked. Then instead of returning the url on the submitSurvey() function call router.navigate() (inject the router first.)
html
<button class="btn btn-primary" (click)="submitSurvey()" routerLinkActive="active">Finish</button>
ts
constructor(private router: Router) { }
public submitSurvey(): void {
// Make sure everything is answered
const allOKClientSide: boolean = this.surveyService.checkEntireForm(this.thisSurvey);
if (allOKClientSide) {
if (this.surveyService.checkFormOnline(this.thisSurvey).subscribe()) {
this.router.navigateByUrl('/placeOne');
return;
}
}
this.router.navigateByUrl('/placeTwo');
}
You want your method to be called when the button is clicked. You can do this by using (clicK):
Instead of
[routerLink]="submitSurvey()"
do
(click)="submitSurvey()"
Then you use the router in your class to do the navigation:
constructor(private router: Router) {}
submitSurvey() {
// ...
this.router.navigate(['/placeOne']);
}
I think the question title is self-explanatory, maybe just a precision when I say while "he or she is browsing" I am thinking in terms of propagation or signal.
I don't want that he or she has to browse another place just to figure out that the Identity SecurityStamp has changed and has been signed out to be then redirected to the home page, I am already doing this but I am wondering if there a framework (I suspect most likely JS) that would make the operation a bit more "real-time".
[EDIT]
Probably a job for SignalR, I haven't tried this out, yet.
I managed to get a working solution with SignalR
First, pay attention to the order in which SignalR is setup in the Startup.Auth.cs How to send message via SignalR to a specific User(Identity Id)? and also create an implementation of the IUserIdProvider that is going to be registred only AFTER the Cookies and OwinContext in order to make it able to leverage the Identity User field (i.e. non-null).
public partial class Startup
{
public void ConfigureAuth(IAppBuilder appBuilder)
{
// Order matters here...
// Otherwise SignalR won't get Identity User information passed to Id Provider...
ConfigOwinContext(appBuilder);
ConfigCookies(appBuilder);
ConfigSignalR(appBuilder);
}
private static void ConfigOwinContext(IAppBuilder appBuilder)
{
appBuilder.CreatePerOwinContext(ApplicationDbContext.Create);
appBuilder.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
appBuilder.CreatePerOwinContext(LdapAdEmailAuthenticator.Create);
}
private static void ConfigCookies(IAppBuilder appBuilder)
{
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>
(
TimeSpan.FromHours(4),
(manager, user) => user.GenerateUserIdentityAsync(manager)
)
}
});
appBuilder.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
appBuilder.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
appBuilder.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
}
private static void ConfigSignalR(IAppBuilder appBuilder)
{
appBuilder.MapSignalR();
var idProvider = new HubIdentityUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
}
}
public class HubIdentityUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
return request == null
? throw new ArgumentNullException(nameof(request))
: request.User?.Identity?.GetUserId();
}
}
Second, declare a hub on the server-side
public class UserHub : Hub
{
}
Third, in a controller (API or not) where a change that involves a logout of a specific user, force a signout + an update of the identity securitystamp:
var userHub = GlobalHost.ConnectionManager.GetHubContext<UserHub>();
userHub.Clients.User(userId).send("Roles added: " + rolesToAdd.Join() + Environment.NewLine + "Roles removed: " + rolesToRemove.Join());
return Request.CreateResponse(HttpStatusCode.OK);
Fourth, use the hub on the JS client-side, I created a partial view which is only used when the current user is authenticated, LoggedOutPartialView.cshtml:
#if (Request.IsAuthenticated)
{
<div class="modal fade" id="loggedOutModal" tabindex="-1" role="dialog" aria-labelledby="loggedOutModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="loggedOutModalLabel">Notification</h4>
</div>
<div class="modal-body">
<h6 class="align-center">Sorry, but it seems that you just have been logged out!!!</h6>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script>
$(function() {
var userHub = $.connection.userHub;
console.log(userHub.client);
userHub.client.logout= function (message) {
$('#loggedOutModal').modal('show');
};
$.connection.hub.start().done(function () {
});
});
</script>
}