onClick event in Dart Web - html

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.

Related

Angular updating values across views

I have an Angular app that has the following:
One component has a text input and a button. The user fills in the text input and clicks the button. This updates a the URL for a router link.
The router link loads a component called view and it in turn reads the URL parameter from the router link and places that value in a service and displays it on the component so I know it worked.
So if the user type 'abc' in the text input then the router link URL would be /view/abc. So 'abc' will be displayed in the view component. Sometimes users will paste a router link like /view/def. This works to update the view component.
The part I can't get to work is to update the text input box in the other component to reflect the current value of the pasted link.
I tried using 'AfterViewChecked' to read the value from the service but that executes before the service value is updated so it is always incorrect.
These cannot bind to the same variable because this will eventually turn into a web service call and I don't want the service to be updated while the user is typing into the text input box, only when they click the button.
I'm not sure where else to look. Any searching I do just brings up data binding, but that is not my problem.
The relevant files are below but the full test sample code is on StackBlitz at https://stackblitz.com/edit/github-jwr6wj.
If you change the URL in the text input and click the button the URL display below will update. But if you paste in the pseudo URL https://github-jwr6wj.stackblitz.io/view/http%253A%252F%252Fwww.ebay.com%252F the URL displayed below will update correctly but I can't figure out how to update the text input to reflect what came in with the URL.
update.service.ts contains the URL that is the current one. This service will also load the data from a web service.
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class UpdateService {
url: string = "http://www.google.com/";
constructor() { }
}
view.component.ts is where the data selected by the user will be displayed. It parses the URL parameter for the data and updates the service with it.
import { ActivatedRoute, ParamMap } from '#angular/router';
import { UpdateService } from '../update.service';
#Component({
selector: 'app-view',
templateUrl: './view.component.html',
styleUrls: ['./view.component.css']
})
export class ViewComponent implements OnInit {
constructor(public activatedRoute:ActivatedRoute, public updateService: UpdateService) { }
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) =>{
this.getUrl(paramMap);
});
}
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get("url");
if (incomingUrl == null) {
this.updateService.url = "http://www.google.com/";
} else {
this.updateService.url = decodeURIComponent(incomingUrl);
}
}
}
view.component.html
<p>URL: {{updateService.url}}</p>
toolbar.component.ts is where the user will enter they request. sourceUrl is the variable that will be updated when the user types. However I also want it to update when the page is visited via the browser URL with the correct data as part of that URL. I can send data to the view component via the router but I can't find out how to send data back to the toolbar component.
import { UpdateService } from '../update.service';
#Component({
selector: 'app-toolbar',
templateUrl: './toolbar.component.html',
styleUrls: ['./toolbar.component.css'],
})
export class ToolbarComponent implements OnInit {
sourceUrl: string = '';
constructor(private updateService: UpdateService) {}
ngOnInit(): void {
this.sourceUrl = this.updateService.url;
}
getViewUrl(): string {
return '/view/' + encodeURIComponent(this.sourceUrl);
}
}
toolbar.component.html
<div class="col-sm-12">
<input type="text" [(ngModel)]="sourceUrl" />
<a class="btn btn-primary" [routerLink]="getViewUrl()">
<span class="fa fa-eye"></span>
</a>
</div>
One way to share data between components is using a Service and Observables. Change your url in the Service to be BehaviorSubject with an initial value.
The way BehaviorSubject works is that you emit values from components to update the Observable in the Service. The BehaviorSubject behaves both as an Observer and Observable.
Essentially, an Observer is an object that listens to events, in this case, updating the URL. An Observable is an object that components listen to for updates or changes. In this case, the View Component listens to the BehaviorSubject for this update to the URL.
Service
export class UpdateService {
private url$ = new BehaviorSubject<string>('www.google.com');
public readonly url: Observable<string> = this.url$.asObservable();
constructor() {}
}
Toolbar Component
export class ToolbarComponent implements OnInit {
sourceUrl: string = '';
constructor(private updateService: UpdateService) {}
ngOnInit(): void {
this.updateService.url.subscribe((str) => {
this.sourceUrl = str;
});
}
getViewUrl(): string {
return '/view/' + encodeURIComponent(this.sourceUrl);
}
}
View Component
export class ViewComponent implements OnInit {
constructor(
public activatedRoute: ActivatedRoute,
public updateService: UpdateService
) {}
ngOnInit(): void {
this.activatedRoute.paramMap.subscribe((paramMap: ParamMap) => {
this.getUrl(paramMap);
});
}
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get('url');
if (incomingUrl == null) {
this.updateService.url.next('http://www.google.com/');
} else {
this.updateService.url.next(decodeURIComponent(incomingUrl));
}
}
}
View Component HTML
<p>URL: {{ updateService.url | async }}</p>
You are right to try with AfterViewChecked because it's just a timing issue. What you could do is have url inside updateService defined as a BehaviourSubject, so that at the moment it's updated in your view component, you see the change in the toolbar component.
Inside the service :
public url$: BehaviorSubject<string> = new BehaviorSubject("http://www.google.com/");
Inside the view component ts :
getUrl(paramMap: ParamMap): void {
const incomingUrl = paramMap.get("url");
if (incomingUrl == null) {
this.updateService.url$.next("http://www.google.com/");
} else {
this.updateService.url$.next(decodeURIComponent(incomingUrl));
}
}
And inside the view component HTML : (you can also subscribe to the Behaviour Subject directly inside the ts)
<p>URL: {{updateService.url$ | async}}</p>
And you will also have to deal with the fact that the url is a Subject inside the toolbar component ts!
Good luck, let me know if this is not clear!

Results not displayed with click event (Angular)

I'm new to Stackoverflow and to Angular. I'm writing a frontend app (an ecommerce) in Angular. I binded a click event to a button of a mat-menu in order to have the following behavior: when I click on the button I want to display the products of the category that has been clicked. For example, if I click the button "Make-up" I want that all the products with category "Make-up" will be displayed. The problem is that when I click on the button nothing is displayed on the browser, but I suppose that the logic of the app works because if I open the console of the browser the producs of the selected category are printed out.
product.component.html:
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="categoryFilter('Make-up')"> Make-up</button>
...
</mat-menu>
product.component.ts:
#Component({
selector: 'app-product',
templateUrl: './product.component.html',
styleUrls: ['./product.component.css'] })
export class ProductComponent implements OnInit {
public products: Product[] = [];
public product!: Product;
constructor(private productService: ProductService) { }
ngOnInit() {
this.getProducts();
}
public getProducts(): void{
this.productService.getProducts().subscribe(
(response: Product[]) => {
this.products= response;
},
(error: HttpErrorResponse) => {
alert(error.message);
}
);
}
public categoryFilter(category: string): void{
this.productService.getProductsByCategory(category).subscribe(
(response: void) => {
console.log(response);
},
(error. HttpErrorResponse)=>{
alert(error.message);
}
)
} }
I think you have missed binding the response to a property, and then using that property to display the data on your browser.
I don't know the response type, for demo purposes let's say it's a string.
First, you'll need to create a property just like you created public product!: Product; we'll call it categoryName: string
In the click event, you'll have to bind this property with your response, hence it should look something like this:
public categoryFilter(category: string): void{
this.productService.getProductsByCategory(category).subscribe(
(response: string) => {
this.categoryName = response;
console.log(response);
},
(error. HttpErrorResponse)=>{
alert(error.message);
}
)
}
Now You'll have to bind that categoryName in your HTML so that it can be displayed. You can use Angular's text interpolation which uses curly brackets {{}} to display string values in the HTML.
Hence your HTML will look like this:
<mat-menu #menu="matMenu">
<button mat-menu-item (click)="categoryFilter('Make-up')"> {{ categoryName }} </button>
...
</mat-menu>
I advise you to read Angular's Guide as you proceed further.
Angular's text interpolation: https://angular.io/guide/interpolation

Angular - Dynamically load html that includes angular markups

In Angular 9+ I can successfully convert a string to a html and then load that that html using innerHtml and bypassSecurityTrustHtml().
My question is it possible to also dynamically load/render the converted html to include and recognise angular/javascript markup language eg *ngIf, handle bars and click events.
Below is the code and stackblitz at the attempt so far but as you can see it doesn't recognise the markup.
https://stackblitz.com/edit/dynamic-angular?file=app/app.component.ts
export class AppComponent implements OnInit {
text: string = "Hello world";
content: any;
constructor(private domSantizer: DomSanitizer) {}
ngOnInit() {
let body: any =
'<div>{{text}}<div><br><button (click)="test()">Test</button>';
this.content = this.domSantizer.bypassSecurityTrustHtml(body);
}
test() {
alert("It works");
}
}
Html
<div [innerHTML]="content"></div>
I have researched and tried many solutions.
My research and trial results are below.
html
<div #container></div>
typescript side as below
export class AppComponent implements OnInit {
#ViewChild("container", { read: ViewContainerRef })
container: ViewContainerRef;
constructor(private compiler: Compiler) {}
text: string = "asdasd";
ngOnInit() {
this.addComponent(
`<div>{{text}}<div><br><button (click)="test()">Test</button>
`,
{
text: "Hello word",
test: function() {
alert("It's work");
}
}
);
}
private addComponent(template: string, properties?: any = {}) {
#Component({ template })
class TemplateComponent {}
#NgModule({ declarations: [TemplateComponent] })
class TemplateModule {}
const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = mod.componentFactories.find(
comp => comp.componentType === TemplateComponent
);
const component = this.container.createComponent(factory);
Object.assign(component.instance, properties);
// If properties are changed at a later stage, the change detection
// may need to be triggered manually:
// component.changeDetectorRef.detectChanges();
}
demo
some posts I have reviewed
compile dynamic Component
angular-html-binding
I think it makes the most sense :)

Angular: ExpressionChangedAfterItHasBeenCheckedError when trying to disable button

I use mat-dialog to edit details of my profile page. I'm getting an ExpressionChangedAfterItHasBeenCheckedError when I click the 'Edit age' button and the dialog window pops up.
I decided to extract the styling of all edit dialogs into a single edit.component:
edit.component.html
<div class="navigation-control">
<mat-icon (click)="onCancelButtonClicked()"
class="close-button">close</mat-icon>
</div>
<div class="content-main">
<ng-content select=".content-main"></ng-content>
</div>
<div class="content-bot">
<button mat-raised-button
(click)="onCancelButtonClicked()">Cancel</button>
<button mat-raised-button
(click)="onActionButtonClicked()"
[lnDisableButton]="actionButtonDisabled">{{actionButtonValue}}</button>
</div>
edit.component.ts
#Component({ selector: 'ln-edit', ... })
export class EditComponent {
#Input() actionButtonValue: string;
#Input() actionButtonDisabled: boolean;
#Output() cancelButtonClicked = new EventEmitter<void>();
#Output() actionButtonClicked = new EventEmitter<void>();
onCancelButtonClicked() {
this.cancelButtonClicked.emit();
}
onActionButtonClicked() {
this.actionButtonClicked.emit();
}
}
To avoid the ExpressionChangedAfterItHasBeenCheckedError when trying to disable buttons and controls, I used this snippet. But that didn't solve this issue.
disable-button.directive.ts
#Directive({ selector: '[lnDisableButton]' })
export class DisableButtonDirective {
#Input('lnDisableButton') isDisabled = false;
#HostBinding('attr.disabled')
get disabled() { return this.isDisabled; }
}
The following is the contents of a mat-dialog window. This gets instantiated when I click the 'Edit age' button. When I remove the [actionButtonDisabled]="actionButtonDisabled", the error goes away, but obivously I need that line to make the functionality disable the button.
age-edit.component.html
<ln-edit [actionButtonValue]="actionButtonValue"
[actionButtonDisabled]="actionButtonDisabled"
(cancelButtonClicked)="onCancelButtonClicked()"
(actionButtonClicked)="onActionButtonClicked()">
<form [formGroup]="ageForm"
class="content-main">
<ln-datepicker formControlName="birthday"
[appearance]="'standard'"
[label]="'Birthday'"
class="form-field">
</ln-datepicker>
</form>
</ln-edit>
I handle the disabling/enabling the button in the 'ts' part of the mat-dialog popup.
age-edit.component.ts
#Component({ selector: 'ln-age-edit', ... })
export class AgeEditComponent implements OnInit, OnDestroy {
ageForm: FormGroup;
private initialFormValue: any;
actionButtonDisabled = true;
private unsubscribe = new Subject<void>();
constructor(
private editPhotoDialogRef: MatDialogRef<AgeEditComponent>,
private fb: FormBuilder,
#Inject(MAT_DIALOG_DATA) public dialogData: Date) { }
ngOnInit() {
this.initializeAgeForm();
this.loadDataToAgeForm(this.dialogData);
this.trackFormDistinct();
}
private initializeAgeForm(): void {
this.ageForm = this.fb.group({
birthday: null,
});
}
loadDataToAgeForm(birthday: Date | null): void {
if (!birthday) { return; }
this.ageForm.setValue({ birthday });
this.initialFormValue = this.ageForm.value;
}
get birthdayAC() { return this.ageForm.get('birthday') as AbstractControl; }
get actionButtonValue(): string {
return this.birthdayAC.value ? 'Update age' : 'Add age';
}
onCancelButtonClicked(): void {
this.editPhotoDialogRef.close();
}
onActionButtonClicked(): void {
this.editPhotoDialogRef.close({ ... });
}
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
ngOnDestroy() { ... }
}
I suspect this has something to do with content projection, but I'm not sure.
(...or perhaps with my custom 'ln-datepicker'?)
Any ideas?
Thanks.
From what I can tell, the problem resides in trackFormDistinct() method:
trackFormDistinct(): void {
this.ageForm.valueChanges.pipe(
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(val => {
(this.formValueNotDistinct(this.ageForm.value, this.initialFormValue)
|| this.birthdayAC.value === null)
? this.actionButtonDisabled = true
: this.actionButtonDisabled = false;
});
}
Looks like because of this.ageForm.valueChanges, will have different values in the 2 change detection cycles. I think this.ageForm.valueChanges emits due to <ln-datepicker>.
In a tree of form controls, if one node calls setValue, all its ancestors will have to be updated. I've written more about how Angular Forms work in this article.
I'm thinking of 2 alternatives:
skip the first emission of ageForm since it indicates the initialization of the form control tree, so this is irrelevant to the logic inside subscribe's callback.
this.ageForm.valueChanges.pipe(
skip(1),
distinctUntilChanged(), // TODO: needed?
takeUntil(this.unsubscribe)
).subscribe(/* .... */)
initialize actionButtonDisabled with false, since the error complains that it switched from true to false
actionButtonDisabled = false;

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>