In my app.component.html:
<html>
<head></head>
<body>
<header *ngIf="loggedIn && showHeader"></header>
<router-outlet></router-outlet>
</body>
</html>
In my app.component.ts:
export class AppComponent {
constructor(private HeaderService: HeaderService, private AuthService: AuthService) { }
get loggedIn(): boolean { return this.AuthService.getUserState(); }
get showHeader(): boolean { return this.HeaderService.getToggleState(); }
}
In my header.service.ts:
We created this service because there are other specific components after login where the header also needs to be hidden.
#Injectable({
providedIn: 'root'
})
export class HeaderService {
showHeader = true;
constructor() { }
setToggleState(state: boolean) {
this.showHeader = state;
}
getToggleState() { return this.showHeader; }
}
Now, in my login.component, the header should be invisible.
What's happening right now, is that there's a brief moment of flicker (seems to happen when you login and then logout, and return to login page) where the header is visible before it's hidden (and yes, also throws up ExpressionChangedAfterItHasBeenCheckedError).
What's the best way to achieve this? Should I just set showHeader to false by default?
login.component.ts
export class LoginComponent implements OnInit {
ngOnInit() {
// To hide the header
this.HeaderService.setToggleState(false);
}
}
When you use *ngIf, the element is not in the DOM (if the condition is false) and will be placed into the DOM in runtime.
Instead, you could use [hidden]="<your condition>", because then your element will be present in the DOM even though the condition is false. If then your condition changes to true, the elements' opacity will be changed from 0 to 1, which makes the element visible.
Now, in order to get a smoother transition, you can put some CSS on your DOM element like this:
.your-element {
transition: visibility 0.5s;
}
By that you get a hover-like effect.
Also, for your second problem (short flickering of the header before the data is there): This can be explained because you initialize showHeader with true. So it shows up at first, then it suddenly disappears when the service is initialized.
So in that case just set it to false at initialization.
Hope that helps :)
You can simply add
ngOnDestroy(){
this.service.setToggleState(true);
}
in login component.
You can set default value to true in service. then when you need to not display the header in which components then set ngoninit to false and ngondestroy to true to avoid flickering.
see here for example https://stackblitz.com/edit/angular-krit8a
Related
I am trying to write a directive that turns the content of a paragraph to uppercase when you hover your mouse over it. I am not getting any errors whatsoever - it just does not work. I have written a similar code before that highlights the text to a certain color, which worked. Why wouldn't it also work when changing the text to uppercase?
filter.component.html
<p appToUpperCase>String to uppercase</p>
to-upper-case.directive.ts
import { Directive, HostListener, ElementRef } from '#angular/core';
#Directive({
selector: '[appToUpperCase]'
})
export class ToUpperCaseDirective {
constructor(public el: ElementRef) {
}
#HostListener('mouseenter2') onMouseEnter() {
this.el.nativeElement.value = this.el.nativeElement.value.toUpperCase();
}
}
EDIT: As #Ilya Rachinsky suggested, I have changed the event name from mouseenter2 to mouseenter and it still does not work.
Your directive structure looks fine. I guess you forgot to include it into the list of declarations on the module, so the directive will be available for the templates. Additionally, there is no 'value' property on 'p' element, you need to use innerHTML as previously suggested.
Checkout my example: https://stackblitz.com/edit/angular-ivy-uppercase-directive?file=src%2Fapp%2Fto-upper-case.directive.ts
You have to use correct event name - mouseenter instead mouseenter2
I have a problem:
I want to get the position of an element as values. Like x= 10 and y = 30 for example. After that i want use them to update the position of the element next time. Like first of all element A was on position x=5 and y=5. Then i drag the position and get the positions x=10 and y=30. Then i'm setting the position for the next time and update the position.
Please Help!
Here is an example on Stackblitz using getBoundingClientRect() to get all the data you need, and here is the code:
.ts :
import {
Component,
ElementRef,
ViewChild,
AfterViewInit,
AfterContentChecked,
AfterViewChecked
} from "#angular/core";
/**
* #title Basic Drag&Drop
*/
#Component({
selector: "cdk-drag-drop-overview-example",
templateUrl: "cdk-drag-drop-overview-example.html",
styleUrls: ["cdk-drag-drop-overview-example.css"]
})
export class CdkDragDropOverviewExample implements AfterViewChecked {
#ViewChild("block") block: ElementRef;
constructor() {}
ngAfterViewChecked() {
let datas = this.block.nativeElement.getBoundingClientRect();
console.log("datas = ", datas);
}
onDrop(item: any) {
console.log("item = ", item.target.getBoundingClientRect());
}
}
.html :
<div class="example-box" cdkDrag #block (mouseup)="onDrop($event)">
Drag me around
</div>
.css :
.example-box {
width: 200px;
height: 200px;
border: solid 1px #ccc;
}
Note this is using cdkDrasg from Angular (I based the example from a stackblitz of the documentation) so you'll need to adapt a bit if you use something else for the drag & drop functionality.
Also, is you plan to edit the position manually (through typescript), consider using Renderer2 instead of using the ViewChild directly.
I have a component A which only contain a div with an id and a buttons that renders a component inside the div using innterHTML document.getElementById('my-router-outlet').innerHTML = '<app-component-b-page></app-component-b-page>';. But this is not rendering I wonder why?.
I'm trying to avoid using ngIf to be a selector for which component should be rendered for performance reason. Also if I clear the innerHTML does the resources of that component will be cleared?
Okay so a few things here
innerHTML = '<app-component-b-page></app-component-b-page>' is never going to work, angular wont recognise the angular component tag from a innerHTML call
using *ngIf wont affect the performance of the page, so doing the following
<app-component-b-page *ngIf="value === true"></app-component-b-page>
is probably you best option here
If you really don't want to use *ngIf you can use #ViewChild and ComponentFactoryResolver
In your HTML
<!-- this is where your component will be rendered -->
<div #entry></div>
In your component
import { Component, OnInit, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '#angular/core'
import { YourComponent } from ... // import the component you want to inject
// ...
export class ...
#ViewChild('entry', {read: ViewContainerRef, static: true }) entry: ViewContainerRef;
constructor(
private _resolver: ComponentFactoryResolver
) {}
showComponent() {
const factory = this._resolver.resolveComponentFactory(YourComponent);
// this will insert your component onto the page
const component = this.entry.createComponent(factory);
}
// and if you want to dynamically remove the created component you can do this
removeComponent() {
this.entry.clear();
}
You are adding the element to the dom directly and it's not rendered by Angular.
You should go for the *ngIf.
This is my use case:
In the WelcomeScreen I have code like this:
class WelcomeScreen extends Component {
render() {
const {
checkoutState,
} = this.props;
if (checkoutState.status === TRYING_TO_BUY) {
return this.renderPurchaseForm(plan);
}
return this.renderWelcome();
}
When the user visit /welcome, he can hit a purchase button, which will dispatch an action and set the checkoutState.status to TRYING_TO_BUY. The Welcome will be rerendered by calling renderPurchaseForm
Within renderPurchaseForm, it will render a ArticlePurchaseBlock
renderPurchaseForm() {
const { articleId } = this.props;
return (
<ArticlePurchaseBlock
articleId={articleId}
/>
)
and in the block, the class will try to update the url to reflect that it is in an input form
import { withRouter } from 'react-router';
class ArticlePurchaseBlock extends Component {
componentWillMount() {
const { history } = this.props;
history.push(URL_BUY_ARTICLE);
}
render() {
// render a redux-form
}
}
export default withRouter(ArticlePurchaseBlock);
You can see the history.push(URL_BUY_ARTICLE); is called in componentWillMount.
Now the problem is: when the user in the purchase form, if a user wants to go back to previous url (/welcome) , he can't. It is because the state of checkoutState.status is still TRYING_TO_BUY. The welcome is always rendered to the form.
Is there any where within the ArticlePurchaseBlock I can monitor the go back event and unset the state? I do not plan to use redux-saga-router yet because of time constraint.
I am using react-router v4
I designed a router for this exact problem. It's excessively difficult with react-router. https://github.com/cellog/react-redux-saga-router. For your code:
https://gist.github.com/cellog/0731f7e1ba8f9009f6b208c2bd15aa16
The entire thing can be done in 1 line of code, and your routes look almost identical to react-router, with 1 additional line for mapping param or url change to action.
Below is part of code in parent component, I already get the enable value from eventEmitter in its child component, which is enable=true.
<img src="{{currentImg}}" alt="Play Image not found" (click)="onMouseClick()">
<pause-button (isPauseBtnClicked)="enable = $event"></pause-button>
status is: {{enable}}
Then how can I assign a value for currentImg="someImg.png" after it listened the eventEmitter(enable=true)? Should I write a function? if so, how can I call that function in img tag without any mouse event?
I konw with mouse click event, things becomes easier, currentImg can be assign a value inside function.
onMouseClick() {
this.currentImg = this.clickedImg;
}
Look I don't know what you want to achieve. But writing this answer by thinking that you want to go with EventEmitter way without calling any mouseevent.
Note: Your expectation might be different. But It might help you out. If doesn't, kindly use it as a reference. I might have understood something completely different but purpose is not to guide you in wrong way
<img src="{{currentImg}}" alt="Play Image not found" (click)="onMouseClick()">
<pause-button (isPauseBtnClicked)="fire($event)"></pause-button><br>
status is: {{enable}}<br> // haven't played with status
{{currentImg}}
boot.ts
fire(arg) {
console.log('test start');
//this.animate.subscribe((value) => { this.name = value; });
this.currentImg=arg;
console.log(arg);
}
Working Plunker
PasueButton.ts
#Component({
selector: 'pause-button ',
template: `
________________________________________________________
<br>
I'm child
<br>
<img src="img path" (click)="clickMe()"/>
<= click the img
<br>
_____________________________________________________
`
,
})
export class PasueButton implements OnInit {
#Output() isPauseBtnClicked: EventEmitter = new EventEmitter();
constructor() {
console.log('Constructor called');
}
ngOnInit() {
console.log('onit strat');
}
clickMe()
{
this.isPauseBtnClicked.next('child Img path is copied');
}
}