How to create angular2 loading indicators button - html

How can I create simple indicators loading button with directive feature in angular2, so then can be use everywhere.also with control access in parent component?
// Something like this
<button loading [waitUntil]="listenToParent">Submit</button>

You can create a directive for the same like this:
#Directive({
selector:'[loading]',
inputs: ['waitUntil']
})
class Loading {
private dataPromise: Promise;
constructor(el: ElementRef){
}
set waitUntil(data:Promise) {
this.dataPromise = data;
this.dataPromise.then(function(message) {
alert(message);
})
}
}
Component for the implementation of the same:
#Component({
selector: 'my-app',
template: `
<h2>Hello World</h2>
<button loading [waitUntil]="thePromise">button</button>`,
providers: [],
directives: [Loading]
})
export class App implements ngAfterViewInit{
name:any;
thePromise: Promise ;
constructor() {
this.thePromise = new Promise((resolve, reject) => {
setTimeout(function() { resolve("API call complete hide loader.");}, 1000);
});
}
ngAfterViewInit(){
}
}
From above example, you can see how a promise that was declared in the parent was passed and fulfilled in the directive, in the constructor of the directive you get the elementRef which can be used to manipulate the element, so you can show a loading symbol or disable the button element till the promise is fulfilled, once is promise is fulfilled the button can be enabled etc. depending on the requirement.
Plnkr for the same: http://plnkr.co/edit/IptHfR?p=preview

Related

I need to have a 'container' template that has to show MyComponent upon certain condition 'externalCondition' MyComponent uses Form and formValidation

container.html
<div ngIf="externalCondition"> <!--Initially this is false. Later became true --!>
<my-component #MyComponentElem > </my-component>
<button [disabled]= "!myComponentElemRef.myDetailsForm.valid" (click)="myComponentElemRef.AFunctionInsideComponent()"> </button>
</div>
container.ts
#Component({
selector: 'my-component',
templateUrl: './comm-roleplay-end2end.html',
styleUrls: ['./comm-roleplay-end2end.scss']
})
export class Container {
#ViewChild('MyComponentElem', { static: true }) private myComponentElemRef: MyComponent;
}
mycomponent.ts
#Component({
selector: 'my-component',
templateUrl: './my-component.html',
styleUrls: ['./my-component.scss']
})
export class MyComponent {
public externalCondition:Boolean = false;
public myDetailsForm: FormGroup;
constructor()
{
//my form builder
}
//
public AFunctionInsideComponent()
{
}
}
Based on myDetailsForm.valid, I need to turn on something in my container.
Issue is that myComponentElemRef is 'undefined' because the element is not created initially. It is visible only after 'externalCondition' becomes true. Upon button click, I need the FormGroup of MyContent to be visible.
But it gets stuck in script error due to
TypeError: reading undefined (myComponentElemRef)
Please suggest the best mechanism to handle the situation
try using [hidden] instead of ngIf, that way the component is created but not visible

calling back end only for particular component in Angular

I have Tags Components in my project and I reused that component in other components. In my Tags component ngOnInit, I called backend to get all the existing tags. The problem I have right now is that call is applied to every other components even though the call is not needed at other components other than Edit Components. Since I only need the backend call to show existing tags just for Edit Components, I tried to move that call to Edit Components ngOninit but it didn't show me the existing tags anymore. I would be really appreciated if I can get any help or suggestion on how to fix this.
Tags Component TS
ngOnInit(): void {
this.tagService.getAllTagsByType('user').subscribe((normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
})
}
If i add this call in Tags Component, it show me all the existing tags in drop down. I tried to move this to Edit component ngOnIt since I only want Eidt Component to use that call but It didn't show me existing tags anymore.
Tags.Service.ts
getAllTagsByType(tagType: any){
return this.http.get<Tag[]>(`${environment.api.chart}/tags/all/${tagType}`).pipe(first());
}
You could try to setup a flag to trigger the backend call using #Input.
tags.component.ts
import { Component, OnInit, Input } from '#angular/core';
export class TagsComponent implements OnInit {
#Input() getAllTags = false;
ngOnInit(): void {
if (this.getAllTags) { // <-- check here
this.tagService.getAllTagsByType('user').subscribe(
(normalTags) => {
this.loading = true;
if (normalTags)
this.allnormalTags = normalTags;
this.allnormalTags.forEach(normalTags => {
this.allTagNames.push(normalTags.tag);
});
this.loading = false;
},
error => {
// handle error
}
);
}
}
}
Now pass the value true to getAllTags when you wish to make the backend call. Since ngOnChanges hook is triggered before ngOnInit, the call will not be made if the property isn't passed in the component selector.
<!-- edit component -->
<mc-tags
[getAllTags]="true"
[workspace]="workspace"
[removable]="true"
[selectable]="true"
[canAdd]="true" ]
[editMode]="true"
(added)="tagAdded($event)"
(removed)="tagRemoved($event)"
> </mc-tags>
<!-- other components -->
<mc-tags [workspace]="workspace"></mc-tags>
Try to use RxJS. You should keep your Tags Data in TagService as a Subject (observable). Btw it is always best practise to store data in service layer.
TagService:
#Injectable({
providedIn: 'root'
})
export class TagService {
tagsSource = new BehaviorSubject<Tag[]>(null);
allnormalTags$ = this.tagsSource.asObservable();
getAllTagsByType(type: string){
http.request.subscribe(resultData => this.tagsSource.next(resultData))
}
}
Then in your component you can check whether data are already loaded and don't call it again.
export class ProductListComponent implements OnInit {
constructor(private tagService: TagService) { }
ngOnInit(): void {
if (isNullOrUndefined(this.tagService.tagSource.getValue())
this.tagService.getAllTagsByType('user')
}
P.S. You don't need to explicitly subscribe service observable in your component. Instead you can directly get your data from service subject/observable with async pipe.
<table *ngIf="tagService.allnormalTags$ | async as allnormalTags">
<tbody>
<tr class="product-list-item" *ngFor="let tag of allnormalTags">
<td data-label="name"> {{tag.name}} </td>

Component Interaction #Input

I would like a component to send input to another component. Below is the code .ts and .html. of the two components.
Now the problem is that the html page of the parent component also shows the html part of the child component ... I want the component to pass only one string to the child component
Parent.ts
import ...
#Component({
selector: 'app-parent',
templateUrl: './parent.html',
styleUrls: ['./parent.css']
})
export class ParentComponent implements OnInit {
sostegno : string;
constructor() { }
ngOnInit() { }
avvia1() {
this.sostegno = "xxx";
this.router.navigate(['./xxx'], { relativeTo: this.route });
}
avvia2()
this.sostegno = "yyy";
this.router.navigate(['./yyy'], { relativeTo: this.route });
}
}
Parent.html
<div>
...
</div>
<app-child [sostegno]="sostegno"></app-child>
Child.ts
import ...
#Component({
selector: 'app-child',
templateUrl: './child.html',
styleUrls: ['./child.css']
})
export class ChildComponent implements OnInit {
#Input() sostegno : string;
constructor() { }
ngOnInit() {
console.log(this.sostegno);
}
}
There are some changes which you need to make because looking at the code which your currently have it seems incomplete.
You are using this.router without injecting the Router class in your constructor.
You are using this.route without injecting the ActivatedRoute class in your constructor.
To test that your parent > child interaction is working you can remove your param and instead place a test for the html
<app-child [sostegno]="'Test'"></app-child>
This should work for your ngOnInit function which is inside of your child component. If this works all you need to do now is either initialize sostegno in your parent component else your console log inside your child component will not reflect the changes when you call avvia1 or avvia2 inside of your parent class.
Hope this helps!

onClick event in Dart Web

I am a beginner to Dart-Web. I tried to handle click events in the HTML DOM via dart, but it doesn't seem to work. Below it my current code.
<div>
<button type="button" onclick="(onclick)=clickHandle()">Sign-in</button>
</div>
#Component(
selector: 'todo-list',
styleUrls: ['login_component.css'],
templateUrl: 'login_component.html',
directives: [
MaterialButtonComponent,
MaterialCheckboxComponent,
MaterialFabComponent,
MaterialIconComponent,
materialInputDirectives,
materialInputDirectives,
NgFor,
NgIf,
],
providers: [ClassProvider(LoginService)],
)
class LoginComponent implements OnInit {
final LoginService loginService;
List<String> items = [];
String newTodo = '';
LoginComponent(this.loginService);
#override
Future<Null> ngOnInit() async {
items = await loginService.getTodoList();
}
void add() {
items.add(newTodo);
newTodo = '';
}
void clickHandle() {
print("Button Clicked");
}
String remove(int index) => items.removeAt(index);
}
Please do request any additional files that are needed to answer the question.
onclick="(onclick)=clickHandle()"
is wrong.
It should be
(click)="clickHandle()"
The event is click, onClick is just a property where you can register a handler function to be called when that click event happens, but Angular is registering the handler in onClick for you.

Angular2 functions in template and change detection

Im trying to build a method inside a service that checks whether a navigation button should be showed to the current user based on his permissions or not (this is just cosmetic "security" I know). Therefore this is the button placed inside the template
<button [routerLink]="['/some/where']"
*ngIf="AuthService.isAuthorized(['some', 'where'])">
Personen
</button>
The method AuthService.isAuthorized uses the provided array to run through all available routes and get the required permissions from the particular route's data object:
{
path: 'some',
component: SomeComponent,
data: {
permissions: [
"read:some",
"edit:some"
]
},
children: [
{
path: 'where',
component: SomeComponent,
data: {
permissions: [
"read:where"
]
}
},
]
}
so in this case the permissions ["read:some","edit:some","read:where"] are needed by the current signed in user so that the button would be displayed to him. Working so far!
But since the function is called inside the template it is called multiple times because of angular change detection. How could I change my code so that the function is called only once? Even better if it would only be called once after the authentication finished writing all permissions assigned to the authenticated user into AuthService.permissions
You can make AuthService.isAuthorized() method returns a promise:
#injectable()
export class AuthService {
...
isAuthorized(arr: string[]): Promise<boolean> {
return new Promise(resolve =>{
// your logic here
resolve(yourResult);
});
}
...
}
You can call this method on your ngOnInit of a component (Therefore it will be called once). You pass the return value to a new variable (e.g. isAuthorized) in the component and use this variable in the template instead.
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent implements OnInit {
isAuthorized: boolean;
constructor(private authService: AuthService) {}
ngOnInit() {
this.authService.isAuthorized(['some', 'where']).then(result => {
this.isAuthorized = result;
});
}
}
In the template you can just use isAuthorized variable.
<button [routerLink]="['/some/where']"
*ngIf="isAuthorized">
Personen
</button>
Edit:
If AuthService.isAuthorized() needed to be called only once but for more than one element, code like these may suits your need:
#Component({
selector: "your-component",
templateUrl: "yourTemplate.html"
})
export class YourComponent {
isObjectAuthorized = {} as {
isFirstAuthorized: boolean;
isSecondAuthorized: boolean;
};
constructor(private authService: AuthService) {}
checkForAuthorization(isElementAuthorized, arr: string[]) {
if (isElementAuthorized !== undefined) {
return;
}
this.authService.isAuthorized(arr).then(result => {
isElementAuthorized = result;
});
}
}
And in your template:
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isFirstAuthorized, ['some', 'where'])">
First
</button>
<button [routerLink]="['/some/where']"
*ngIf="checkForAuthorization(isObjectAuthorized.isSecondAuthorized, ['some', 'where', 'else'])">
Second
</button>