I'm using angular 6 and i can't instantiate a components more than one time, I have tryed to see similar question on stack overflow but I didn't find answer
here is html-div-component.html
<div [style.opacity]="opacity" class='div-component' [style.margin- top.px]="marginTop">
and html-div.component.ts
#Component({
selector: 'app-html-div',
templateUrl: './html-div.component.html',
styleUrls: ['./html-div.component.css']
})
export class HtmlDivComponent implements OnInit {
#Input() name;
#ViewChildren('div-component') elements: QueryList<HtmlDivComponent>;
...
constructor() { }
ngOnInit() {
console.log('initializing');
window.addEventListener('scroll', this.scrollHandler, true);
window.onload = (event) => {
...
};
}
...
}
Here the parent component .html
<app-html-div id="get-started-div" name="blue">
<div class="charat-list default-panel mui-panel">
<h2 class="forstyle-h2">/h2>
<p>
<input type="button" value="1">
</p>
<p>
<input type="button" value="2">
</p>
</div>
</app-html-div>
<app-html-div name="green">
<div class="charat-list default-panel mui-panel">
<h2 class="forstyle-h2"></h2>
<p>
<input type="button" value="1">
</p>
<p>
<input type="button" value="2">
</p>
</div>
in my log console i see this
initializing
Thank you for your help
As discussed over comments, console log in ngOnInit will be consoled in browser developer tools as many times as the component used
codesample -
https://stackblitz.com/edit/angular-qwae9v?file=src/app/app.component.html
https://stackblitz.com/edit/angular-dvaleu?file=src/app/hello.component.ts
In your case, it is not issue with the application but the browser developer tools is designed to stack repeated console messages to one and show the number for the repetition
Check this link for reference - https://developers.google.com/web/tools/chrome-devtools/console/ as per Chrome documentation
Message stacking
If a message is consecutively repeated, rather than printing out each instance of the message on a new line, the Console "stacks" the messages and shows a number in the left margin instead. The number indicates how many times the message has repeated.
Just for testing and understanding , try this simple example
for (i = 0; i < 4; i++) {
alert("test")
console.log("test")
}
//ouput you will see in console as 3 test, you will see three alert windows
Is the second instance of <app-html-div> rendering?
It is possible that your first instance of HtmlDivComponent initializes, runs, and fails. If there was a problem in the first instance of the component, that may prevent the rest of the application from loading.
Related
I am trying to learn how component driven development works and I have followed the documents here https://knockoutjs.com/documentation/component-custom-elements.html (Including the many nested links relevant to this topic) however although the TS/JS files are loaded without error the HTML component is never rendered.
Here you can see the the typescript is loaded correctly by RequireJS:
Login-User typescript loaded in browser
And here is the HTML component in the webpage:
HTML component
This is the content within the HTML 'template':
<div class="panel">
<label>Username:</label>
<input type="text" data-bind="Value: $component.Username()" />
<br />
<label>Password:</label>
<input type="text" data-bind="Value: $component.Password()" />
<br />
<label>Valid:</label>
<input type="text" data-bind="Value: $component.ValidUser()" />
</div>
Here is the registration of the HTML template:
const componentName = "Login-User";
ko.components.unregister(componentName);
ko.components.register(componentName, {
viewModel: LoginViewModel,
template: { require: `text!/Views/Components/${componentName}.html` }
});
I do not get any errors in the console but the constructor in the TS file is never hit when adding breakpoints to debug which suggests the me that there was no attempt to actually render the HTML component at all?
I have checked all file paths are correct and deleted and re-compiled the TS files to generate JS files to ensure everything is up to date, I assume I have not properly configured require in some way and so the HTML component is never actually registered however due to no errors being logged I am a bit stuck for where to go next! As I said previously I have read the documentation on Knockout and also for RequireJS however when searching google for issue when implementing a HTML component I seem to only get results for Angular.
Any advice on how to determine the issue would be greatly appreciated, even better if there is any documentation/guides on how to use Knockout/Require/Typescript/HTML Components together which someone could point me at that would be great!
I think I have provided everything need but if not let me know.
Thanks,
Danny
Okay after a couple more hours of trial and error I figured out I had a few problems, for anyone else having this issue try the below resolutions:
I was not calling ko.applybindings();
import * as ko from "knockout";
export default class LoginViewModel {
Username: KnockoutObservable<string>;
Password: KnockoutObservable<string>;
ValidUser: KnockoutComputed<boolean>;
constructor(username: string, password: string) {
this.Username = ko.observable(username);
this.Password = ko.observable(password);
this.computedMethods();
}
private computedMethods(): void {
this.ValidUser = ko.pureComputed(() => {
return this.Username() === "Danny" && this.Password() === "pasword";
});
}
}
const componentName = "login-user";
ko.components.register(componentName, {
viewModel: LoginViewModel,
template: { require: `text!Scripts/Typescript/${componentName}.html` }
});
ko.applyBindings(); << This is important as it actually binds the custom element i.e login-user params="username: 'Danny', password: 'none'"></login-user> and without it nothing will be rendered on the page
After correcting this issue I then got a 404 in console when trying to load the custom element and although the filepath was correct I found that the best way to resolve this was to have the custom element in the same folder as it's TS counter-part:
Before:
template: { require: `text!/Views/Components/${componentName}.html` }
After:
template: { require: `text!Scripts/Typescript/${componentName}.html` }
Google searches recommended installing the following nuget packages although I think only require.text is actually required, if it doesn't resolve your issue it's worth a shot..:
Require Packages
I initially had my components named using camel case i.e Login-User.ts and Login-User.html, I read somewhere that they should be lower case to be valid html 'Tags' and both the .ts and .html files should be named exactly the same
Hope this helps anyone else having issues.
I am trying to create a form in angular
<form name="vm.mechanicalForm">
<div layout="column" layout-padding>
<div layout="row" layout-align="end center">
<md-button class="aq-btn md-accent" type="submit"
ng-show="vm.Auth.check({access: 'EDIT'})"
ng-click="vm.save()"
ng-disabled="vm.mechanicalForm.$invalid">
Save Button
</md-button>
</div>
<div>
<md-input-container class="md-block"
ng-if="vm.building.mainSourceOfCooling === 'CHILLERS'">
<label>Cooler</label>
<input name="numberOfChillers" class="form-control"
type="number"
ng-model="vm.building.numberOfChillers"
required min="0">
<div ng-messages="vm.mechanicalForm.numberOfChillers.$error"
ng-hide="vm.building.numberOfChillers">
<div ng-message="min">
<span class="red">Number of Chillers must be a positive number</span>
</div>
</div>
</md-input-container>
</div>
</div>
</form>
I cannot get any of the states connected to the form, for example vm.mechanicalForm.$invalid/$valid to work. I want to be able to disable my save button when there is a negative number of Coolers entered. Even though the error Number of chillers must be positive is displayed the save button still works. Additionally, I wanted to change the ng-hide on the ng-messages div to hide the div when the form is valid, but that seems to break functionality too. What can I do to correct this error? I have looked around on stack overflow and the answers I have seen seem to suggest I am using the form state correctly.
EDIT:
Here is my controller code:
namespace properties {
export class MechanicalCtrl {
constructor(
public Messages,
public building,
public BuildingService,
private allEnums,
private Auth
) {}
public save() {
this.BuildingService.updateBuilding(this.building)
.then(() => {
this.Messages.success('Successfully updated mechanical information');
})
.catch(() => {
this.Messages.error('Unable to update mechanical information');
});
}
}
angular
.module('properties')
.controller('MechanicalCtrl', MechanicalCtrl);
}
According to the answer here I should be able to reference vm.mechanicalForm in my MechanicalCtrl as this.mechanicalForm (unless I misunderstood that answer). Another thing I have noticed is that I am not able to do this. I had tried debugging this by trying to console.log properties on the form but it is returned as undefined if I do console.log(this.mechanicalForm). Have I done something wrong?
EDIT 2:
I am setting up my template and controller as
.state(...) {
templateUrl: 'my/template/url',
controller: 'MechanicalCtrl as vm',
...
}
I have a working form taking the following HTML markup. No errors or warnings.
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
I transformed it into a custom component, which also works, as shown below.
<app-input-text [info]="'Title'"
formControlName="targetField"
ngDefaultControl></app-input-text>
In my next view, I need to use FormArray as follows - still working code.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<div class="input-element">
<div class="input-caption">Title</div>
<input type="text"
formControlName="targetField"
class="form-control">
</div>
</div>
</div>
Now, I expected that combining both (i.e. being able to use custom input component and being able to form array for components) would post no problem. However, the sample below doesn't work.
<div formArrayName="stuff">
<div *ngFor="let thing of form.controls.stuff.controls; let i = index;"
[formGroupName]=i>
<app-input-text [info]="'Title'"
formControlName="targetField"
class="col-sm-6"></app-input-text>
</div>
</div>
It generates the following error.
No value accessor for form control with path: 'stuff -> 0 -> targetField'
The custom component is design like this (although given that it works in the explicit markup example, I'm not sure if it's relevant information). The only (wild) guess I have might be that value isn't jacked into the form array field somehow.
export class InputTextComponent implements OnInit {
constructor() { this.value = new EventEmitter<string>(); }
#Input() info: string;
#Output() value: EventEmitter<string>;
onEdit(value: any): void { this.value.emit(value); }
}
The group and array creating in the current view is done like this (not sure if this is of any relevance neither, as it works for the explicit HTML markup case).
this.form = builder.group({
id: "",
stuff: builder.array([
builder.group({ targetField: "aaa" }),
builder.group({ targetField: "bbbb" }),
builder.group({ targetField: "cc" })
])
});
Is there a limitation in Angular in this regard that I'm not aware of? I'm rather sure there's not and that I'm just doing something fairly clever simply missing a tiny detail.
I do understand the error but I can't see how it relates to the code. The form can't find the 0th element in the array or that element has no field of that name. Since I do get to see a few rows, I know there must be a 0th element. Since I specified the name of the field, I know there is indeed such. What else am I missing?
I am trying to fire click event (or any other event) on element programatically , In other word I want to know the similar features as offered by jQuery .trigger() method in angular2.
Is there any built in method to do this? ..... if not please suggest how can i do this
Consider the following code fragment
<form [ngFormModel]="imgUploadFrm"
(ngSubmit)="onSubmit(imgUploadFrm)">
<br>
<div class="input-field">
<input type="file" id="imgFile" (click)="onChange($event)" >
</div>
<button id="btnAdd" type="submit" (click)="showImageBrowseDlg()" )>Add Picture</button>
</form>
Here when user click the btnAdd it should fire the click event on imgFile
Angular4
Instead of
this.renderer.invokeElementMethod(
this.fileInput.nativeElement, 'dispatchEvent', [event]);
use
this.fileInput.nativeElement.dispatchEvent(event);
because invokeElementMethod won't be part of the renderer anymore.
Angular2
Use ViewChild with a template variable to get a reference to the file input, then use the Renderer to invoke dispatchEvent to fire the event:
import { Component, Renderer, ElementRef, ViewChild } from '#angular/core';
#Component({
...
template: `
...
<input #fileInput type="file" id="imgFile" (click)="onChange($event)" >
...`
})
class MyComponent {
#ViewChild('fileInput') fileInput:ElementRef;
constructor(private renderer:Renderer) {}
showImageBrowseDlg() {
// from http://stackoverflow.com/a/32010791/217408
let event = new MouseEvent('click', {bubbles: true});
this.renderer.invokeElementMethod(
this.fileInput.nativeElement, 'dispatchEvent', [event]);
}
}
Update
Since direct DOM access isn't discouraged anymore by the Angular team this simpler code can be used as well
this.fileInput.nativeElement.click()
See also https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/dispatchEvent
I also wanted similar functionality where I have a File Input Control with display:none and a Button control where I wanted to trigger click event of File Input Control when I click on the button, below is the code to do so
<input type="button" (click)="fileInput.click()" class="btn btn-primary" value="Add From File">
<input type="file" style="display:none;" #fileInput/>
as simple as that and it's working flawlessly...
This worked for me:
<button #loginButton ...
and inside the controller:
#ViewChild('loginButton') loginButton;
...
this.loginButton.getNativeElement().click();
To get the native reference to something like an ion-input, ry using this
#ViewChild('fileInput', { read: ElementRef }) fileInput: ElementRef;
and then
this.fileInput.nativeElement.querySelector('input').click()
Günter Zöchbauer's answer is the right one. Just consider adding the following line:
showImageBrowseDlg() {
// from http://stackoverflow.com/a/32010791/217408
let event = new MouseEvent('click', {bubbles: true});
event.stopPropagation();
this.renderer.invokeElementMethod(
this.fileInput.nativeElement, 'dispatchEvent', [event]);
}
In my case I would get a "caught RangeError: Maximum call stack size exceeded" error if not. (I have a div card firing on click and the input file inside)
If you want to imitate click on the DOM element like this:
<a (click)="showLogin($event)">login</a>
and have something like this on the page:
<li ngbDropdown>
<a ngbDropdownToggle id="login-menu">
...
</a>
</li>
your function in component.ts should be like this:
showLogin(event) {
event.stopPropagation();
document.getElementById('login-menu').click();
}
I have two screens in my project, MemberList.HTML and EditMember.html.
MemberList.HTML displays all members with Edit link for each member.
When I click on Edit link, it calls the function ng-click="EditMember(member)" and code for EditMember(member) is
$scope.EditMember = function (member) {
var getData1 = angularService.GetMember(member.ID);
getData1.then(function (mem) {
$scope.Member = mem.data;
alert($scope.Member.FirstName);
$location.path('/members/editmember');
}, function (error)
{
alert('Error in getting Member record');
}
);
};
code for EditMember.Html
<div>
<div class="bottom-margin">
<div class="left-label">ID</div>
<div><input type="text" name="txtID" id="txtID" ng-model="Member.ID"/></div>
</div>
<div class="bottom-margin">
<div class="left-label">First Name</div>
<div><input type="text" name="txtFirstName" ng-model="Member.FirstName"/></div>
</div>
</div>
<div>
<div class="bottom-margin">
<div class="left-label"><input type="button" name="btnCancel" value="Cancel" /></div>
<div><input type="button" name="btnSave" value="Save" /></div>
</div>
</div>
Route configuration is
$routeProvider.when('/members/editmember',
{
templateUrl: '/Template/EditMember.html',
controller: 'myCntrl'
});
Now the problem is, in alert it is showing me the First Name but it is not displaying any data in EditMember.Html.
Everything is in same angular CONTROLLER, there is no different controller is used here.
How do I pass $scope with member data to EditMember.Html? What am I doing wrong?
Unlike services, controllers are not singletons in angular. When you changed the location, a new instance of that controller was created, and therefore a new scope.
Personally, I would pass a reference in the URL to the member you want to edit, e.g. /members/edit/1234, and load that data in when the controller loads, or during routing using $routerProvider resolve.
Personally, I would also consider using a different controller for editing vs viewing, and moving any shared functionality into services - just to keep things coherent.
#glennanthonyb, I did something like this....I am using the same controller here.
In route, I have added
$routeProvider.when('/members/editmember/:ID',
{
templateUrl: '/Template/EditMember.html',
controller: 'myCntrl'
});
and in the Controller I have added $routeParams parameter
if ($routeParams.ID != null) {
GetMember();
}
function GetMember() {
var getData = angularService.GetMember($routeParams.ID);
getData.then(function (mem) {
$scope.Member = mem.data;
}, function (error) {
alert('Error in getting records');
});
}
In MemberList.Html instead of calling a function, I am using href
Edit
I am not sure if it is the right way to do it or not but it is working.