Why databinding is not working in Angular 13? - html

I have created an event in my header component and trying to listen it in app component, but it is not working as expected.
In header.component.html, on clicking on "Recipes" it is sending 'recipe' string to "onSelect()" method and on clicking on "Shopping List", it is sending string 'shopping-list' to "onSelect()" method.
<li> Recipes</li>
<li> Shopping List</li>
In header.component.ts , I have created event "featureSelected" which is emitting data, whatever we receive in "onSelect()" method.
#Output() featureSelected = new EventEmitter<string>();
onSelect(feature:string){
this.featureSelected.emit(feature);
}
Now, we are listing event "featureSelected" in app.component.html, if it emits string "recipe" we load, recipe component else we load "shopping-list" component.
<app-header> (featureSelected)="onNavigate($event)"</app-header>
<div class="container">
<div class="row">
<div class="col-md-12">
<app-recipes *ngIf="loadedFeature === 'recipe'"></app-recipes>
<app-shopping-list *ngIf="loadedFeature !== 'recipe'"></app-shopping-list>
</div>
</div>
</div>
app.component.ts
loadedFeature='recipe';
onNavigate(feature:string){
this.loadedFeature=feature;
}
It is not loading shopping-list component on clicking on "Shopping List". I think listener is not working properly even though code looks fine. Please help me in finding the issue and please let me know, if any additional information required.

<app-header> (featureSelected)="onNavigate($event)"</app-header>
First of all this is not correct. You need to have output method inside of app-header like this.
<app-header (featureSelected)="onNavigate($event)"> </app-header>
And with that you can access in the onNavigate($event) and store it to some property.
There is stackblitz with working example StackBlitzDemo
If this helps mark it as answer pls.

Related

Angular. Getting wrong data from ngb-panel

I use ngb-accordion in my app. I am trying to get data from every panel but when the first panel is opened click from the second panel returns me wrong data.
Result
I think the problem is the event which raises when input file changes.
Stackblitz Link
I will be glad if someone give me a hint for solving this problem.
There are few things to note in your code.
Your *ngFor is at ngb-accordion which is creating a new accordion for every loop, instead of creating multiple panel within one accordion.
Fix: <ngb-panel *ngFor="let data of datalist; let i = index">
You are using the same label for all three panels, because of which your first panel is opening every time, regardless of which panel you are clicking.
Fix: <label [for]="'image-input-' + i"> and <input ... [id]="'image-input-' + i"
The modal that opens after image selection has no knowledge of which panel it's getting triggered from. So, you have to use your (change)="onFileChange($event, data)" event/function to keep track of selected panel/corresponding data.
Then you can pass that selection from your modal to your processFile(...)
Fix:
export class AppComponent {
...
selectedData: Data;
...
...
onFileChange(event: any, data): void {
...
this.selectedData = data;
}
}
html:
...
<input ... (change)="onFileChange($event, data)>
...
...
<button
...
(click)="processFile(imageInput, selectedData)"
> Done
</button>
Stackblitz Demo

AngularJS dynamic additions to page

We have this AngularJS SP application (smart-mirror) in electron browser, which has user createable extensions.
the extensions are small snippets of html that use angular directives
and use controllers and services.
to install an extension, one has to edit the main page and insert the script tags for the controller and service functions and a <div ng-include= ...> for the snippet of HTML
hardcoded this single page app works great.
but I want to add the capability to this app (opensource) to dynamically load those elements somehow...
adding the tags to the dom works, BUT are not processed correctly.
the HTML is processed before the scripts (from the inserted tags) are run, and when the ng-include inserts the HTML snippet, then controllers are not defined yet...
the body (with the extensions in hard-coded positions commented out)
<body ng-controller="MirrorCtrl" ng-cloak>
<div class="top">
<div class="top-left">
<!-- <div ng-include="'plugins/datetime/index.html'"></div>
<div ng-include="'plugins/calendar/index.html'"></div> -->
</div>
<div class="top-right">
<!-- <div ng-include="'plugins/weather/index.html'"></div>
<div ng-include="'plugins/traffic/index.html'"></div>
<div ng-include="'plugins/stock/index.html'"></div>
<div ng-include="'plugins/tvshows/index.html'"></div>
<div ng-include="'plugins/ha-display/index.html'"></div> -->
</div>
</div>
...
...
<script src="filename.service"/>
<script src= filename.controller"/>
</body>
the calendar extension html (inserted into specific div area of the page)
<ul ng-controller="Calendar" class="calendar fade" ng-show="focus == 'default'" ng-class="config.calendar.showCalendarNames ? 'show-calendar-names' : ''">
<li class="event" ng-repeat="event in calendar" ng-class="(calendar[$index - 1].label != event.label) ? 'day-marker' : ''">
<div class="event-details">
<span class="day">
<span ng-bind="event.startName"></span>
<span ng-if="event.startName != event.endName"> - <span ng-bind="event.endName"></span></span>
</span>
<div class="details calendar-name" ng-bind="event.calendarName"></div>
<span class="summary" ng-bind="event.SUMMARY"></span>
<div class="details" ng-if="event.start.format('LT') != event.end.format('LT')">
<span ng-if="event.startName != event.endName"><span ng-bind="event.start.format('M/D')"></span> <span ng-bind="event.start.format('LT')"></span> - <span ng-bind="event.end.format('M/D')"></span> <span ng-bind="event.end.format('LT')"></span></span>
<span ng-if="event.startName == event.endName"><span ng-bind="event.start.format('LT')"></span> - <span ng-bind="event.end.format('LT')"></span></span>
</div>
<div class="details" ng-if="event.start.format('LT') == event.end.format('LT')">All day</div>
</div>
</li>
</ul>
the calendar extension controller (used by the html)
function Calendar($scope, $http, $interval, CalendarService) {
var getCalendar = function(){
CalendarService.getCalendarEvents().then(function () {
$scope.calendar = CalendarService.getFutureEvents();
}, function (error) {
console.log(error);
});
}
getCalendar();
$interval(getCalendar, config.calendar.refreshInterval * 60000 || 1800000)
}
console.log("registering calendar controller")
angular.module('SmartMirror')
.controller('Calendar', Calendar);
the calendar extension service (used by the controller, shortened for this discussion)
(function () {
'use strict';
function CalendarService($window, $http, $q) {
...
...
return service;
}
console.log("registering calendar service")
angular.module('SmartMirror')
.factory('CalendarService', CalendarService);
} ());
so a user wanting to add an extension would have to create these files,
and edit the main page HTML and insert them
<div ng-include src="filename.html"></div>
in the right place and then add the
<script src="filename.service" >
and
<script src="filename.controller">
in the right place and order, service needs to be done before the controller,
as controller uses service.
anyhow, it's easy to add code to locate all the extensions and dynamically insert elements into the dom in their respective places... but...
in the hard coded, the scripts are added after the html in the body
so, I added a new script (processed when the page is loaded), which locates and inserts all the elements to support the extensions in the right places..
and then the script ends.... (last one in the hard-coded HTML) and the HTML directives are processed and boom, the dynamically added scripts have not been loaded or processed, so the controllers are not found...
I CAN create a temp HTML file with all this info in it and load THAT instead of dealing with the dynamic loading, but I think its better to resolve this
I have tried creating my own angular directive and compiling that in, but get stuck in a loop
<divinc src="filename.service"></divinc>
the inserted div is correct, as a child of the divinc directive
angular.module('SmartMirror')
.directive("divincl", ["$compile" ,function($compile){
return {
priority: 100,
terminal: true,
compile: function(scope, element, attrs) {
var html = "<div ng-include=\"" + element['incl']+ "\" onload='function(){console.log(\'html loaded\')}'></div>"
var templateGoesHere = angular.element(document.getElementById(element['id']));
templateGoesHere.html(html);
//document.body.innerHTML='';
var v= $compile(templateGoesHere);
//scope.$apply();
return function linkFn(scope) {
v(scope) // Link compiled element to scope
}
}
}
}]);
advice on how to solve this problem.. Thanks
In order to make an angularjs 1.7 application load dynamically extensions, there are 2 ways:
either use "nested angularjs applications", which is clearly an advanced use of angularjs and will require you to communicate between 2 angularjs applications, to use $scope.$apply to tell the other app to update etc..
either don't load them dynamically in the frontend, but in your backend when generating the html page which contains the application. Try to list all the extensions from the start.
I recommend you to forget the use of ng-include too, and the fact of trying to add <script></script> inside a directive of your application.
First, you need to re-understand how an angularjs application is started.
When you load your main application, you have a script in which angular.module, angular.directive, angular.value, angular.config, angular.run ... calls are made. This is the declaration step
If you declare a module MyApp and that in your html you have a DOM element with ng-app="MyApp", angularjs will automatically run angular.bootstrap() on this DOM element in order to start MyApp. The execution of the application starts here. You cannot declare anything anymore in the module MyApp.
Secondly, I think that <script></script> code inside templates is sanitized and removed by angular. Plus, even if you execute the code, since the declaration step has finished, you are not supposed to create new directives or register new services, it won't work.
A good way is that when you load your plugin, you:
Load the script of the plugin from the start, it must declare a new module like MyPlugin1.
In the directive which will contain the plugin, put the code of the link I sent you, which makes possible to insert a sub-application. In the end you will have a <div ng-app="MyPlugin1"></div> inside your directive's template
Then call angular.bootstrap on that node, which will make possible to start the sub application.
If you do this, you can run the sub application, but you didn't pass it parameters. In order to pass it parameters, you can put the code of the module MyPlugin1 inside a function, in order to have an application factory. Then use app.value('param1', parameter1) to initialize the app.
For example:
function declarePlugin1(myParam1, myParam2) {
var app = angular.module('MyPlugin1', []);
// app.directive();
app.value('myParam1', myParam1);
app.value('myParam2', myParam2);
}
And inside the directive call declarePlugin1("test", 42);, which will declare the application MyPlugin1 with the initialized values, and then angular.bootstrap to tell angularjs to start this application.
You can pass callbacks too, in order to communicate between the 2 applications.

How to close an Angular2 modal with a boolean?

I'm new to html and Angular2. Any help with this problem would be much appreciated.
I am placing a form component inside a ng2-modal and I would like the modal to close when the form returns a boolean from an event.
The form is for adding a new class to a database. Its first usage is on another page where it is not inside a modal.
There, upon clicking cancel/submit in the component, I have it return true and then redirect to another url.
For the new implementation I want the modal to close when the form returns true. The problem is that the save/cancel buttons are in the component containing the form. So I can't just use the modal's click event to close it.
Here is my current code:
<modal #addNewClassModal>
<modal-header>
<h4 class="modal-title"><i class="fa fa-cube"></i> Add a new class</h4>
</modal-header>
<modal-content>
<div>
<add-new-class (closeModal)="finishAddingNewClass($event)">
</add-new-class>
</div>
</modal-content>
</modal>
My problem is that I can't figure out how to get the modals close() method to rely on the boolean.
I've tried putting closeModal="addNewClassModal.close()" in different places and switching around the syntax but its not working and I can't find anything online.
You can pass a reference to addNewClassModal to your AddNewClass component:
<add-new-class [modal]="addNewClassModal">
</add-new-class>
And in your AddNewClass component add Input() for modal:
import { Input } from '#angular/core';
import { ModalDirective } from 'ng2-bootstrap/components/modal';
#Input()
modal: ModalDirective; // you can also set type to any instead of ModalDirective
Then in your component you can close modal with hide() function:
this.modal.hide();

Reusing pages in WinJS's Pivot

I'm developing app for Windows Phone 8.1 using WinJS and I used Visual Studio's template for pivot application. My Applications queries external API and displays results in PivotItem. Since there are three very similar queries that reurn same type of data, I'd like to reuse one code for all the sections in Pivot. The PivotItem page consist basically only of ListView with items received from API. My section page javascript looks like this:
var ControlConstructor = WinJS.UI.Pages.define("/pages/bookmarks/sectionPage.html", {
ready: function(element, options) {
//Here I call API based on received option and render the page
}
}
WinJS.Namespace.define("bookmarksApps_SectionControls", {
SectionControl: ControlConstructor
});
My page declaring the Pivot looks like this:
<div class="bookmarks" data-win-control="WinJS.UI.Pivot" data-win-res="{ winControl: {'title': 'BookmarksTitle'} }">
<div class="section1 section" data-win-control="WinJS.UI.PivotItem" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'BookmarksNew'} }">
<div class="sectioncontrol" id="section1contenthost" data-win-control="bookmarksApps_SectionControls.SectionControl" data-win-options="{'section': 'new'}"></div>
</div>
<div class="section2 section" data-win-control="WinJS.UI.PivotItem" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'BookmarksAll'} }">
<div class="sectioncontrol" id="section2contenthost" data-win-control="bookmarksApps_SectionControls.SectionControl" data-win-options="{'section': 'all'}"></div>
</div>
<div class="section3 section" data-win-control="WinJS.UI.PivotItem" data-win-options="{ isHeaderStatic: true }" data-win-res="{ winControl: {'header': 'BookmarksHistory'} }">
<div class="sectioncontrol" id="section3contenthost" data-win-control="bookmarksApps_SectionControls.SectionControl" data-win-options="{'section': 'history'}"></div>
</div>
</div>
Now, when I open the app,pivot page correctly loads and displays first section with data. But when I swipe the different section, new data is loaded (so the ready function is called, but nothing is displayed (page is blank, only PivotItems' headers are visible). But if I swipe back to section1, it contains data, that I want to display in section2.
Is it possible to reuse my SectionPage.html and SectionPage.js in different PivotItems, preferably without too much of boilerplate code?
You need to create custom HTML control which will host these pages, custom control can accept uri as data-win-options, then inside your control you can have updateLayout() which will render the page and append to parentElement.
Sample code in update layout method:
var options = {} //Page options
if (!this._isLoaded) {
this._isLoaded = true;
WinJS.UI.Pages.render(this.uri, this._pageElement, options);
}
I found source of my problem. In page /pages/bookmarks/sectionPage.html I had <div> with an id meant for holding my ListVIew. And I was getting win control for listview using document.getElementById("listViewId").winControl. This is wrong, because then I had three divs with same id (each for every section), so getElementById was always returning same list (the one on the first section).
So I changed getting of the wincontrol to
var discussionList = document.querySelector("#" + contentHost + " .disucssionsListView").winControl;
where contentHost depends on data-win-options received from main page and everything works as expected.

AngularJS ng-modal do not return latest value from form input

I am still new towards AngularJS, I made a simple textarea to handle user input using angular model binding like below code (noted that my ng-app and ng-controller are being injected somewhere else but it is within the entire <div></div>):
HTML:
<div ng-controller="StatusCtrl">
//some other HTML
<div class="sPTabs-holder">
<tabset>
<tab heading="Status">
<div>
<form class="statusPost" enctype="multipart/form-data">
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<textarea type="text" ng-model="inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
</div>
</div>
<div class="form-group no-margin">
<div class="col-md-12 col-sm-12 no-pad">
<button style="width: 12%;" ng-click="postStatus()" class="btn btn-primary btn-sm" type="button">Share</button>
</div>
</div>
</form>
</div>
</tab>
<tab heading="Image">Image</tab>
</tabset>
</div>
</div>
JS:
'use strict';
var Status = angular.module('Status',['ui.bootstrap','ngResource','ngSanitize'])
Status.controller('StatusCtrl', ['StatusService','$resource','$scope','$http', '$timeout', '$sce',
function StatusCtrl(StatusService, $resource, $scope, $http, $timeout, $sce) {
//Usable models
$scope.inputStatus;
//Html-bind
$scope.makeTrust = function(html){
return $sce.trustAsHtml(html);
}
$scope.postStatus = function(){
if ($scope.inputStatus == null){
console.log('Blank post alert');
alert('You cannot post with blank statuses!');
}else{
console.log($scope.inputStatus);
}
}
}]);
My problem is whenever I click on the submit button angular will always pop me with the empty input error even though I have input in the textarea. At first I thought that I made a mistake in my model binding so I have tried out to echo the value in html using {{inputStatus}}, things appeared as it was typed and also when I try to define a default value in $scope.inputStatus = 'default value', the console does indeed echoed 'default value', but the problem is it doesn't store anything that is being typed in the form. What have i done wrong in my code?
Noted that I am not so familiar on how to setup AngularJS in JSFiddle. I apologize in advance if you would like to see the working demo.
**Update 1 - I have narrow down the problem, apparently the problem only occur when I am using angular tabs by Angular Bootstrap. So what happen is if you revise the HTML code, there is this <tabset> section. When declaring the ng-controller after the <tabset> section and everything works like a charm but if you declare it before the <tabset> section, that is where everything mess up.
You should initialize $scope.inputStatus in your controller, otherwise it will pop out an alert windows if you haven't input anything in the textarea (which will initialize or update $scope.inputStatus).
So you change your controller to
$scope.inputStatus = "";
Then everything will work, here is a working demo.
update
If you are using <tabset>, then you are facing child scope problem. <tabset> will create a child scope inside your controller, which means, the scope bind to tabset is the child of scope bind to StatusCtrl.
There are two ways to fix this problem. The first one is accessing the parent scope directly by changing your ngModel to below
<textarea type="text" ng-model="$parent.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
The second one is easier but may looks like a trick, use Dot notation like #lcycook mentioned. In your controller StatusCtrl, declare a dictionary called data
$scope.data = {
inputStatus: ""
};
Then you can access the inputStatus by data.inputStatus anywhere inside the controller scope and you don't need to care about the child scope.
While there is no direct evidence, I suspect your text area is masked inside a child scope. This is common for new AngularJS developers.
While you are learning which directive creates a child scope (e.g. ng-if, ng-repeat), you can avoid this problem with "Dot notation". Which is, wrapping the model inside an object.
You can do this by initializing your ng-model or at least the wrapper object in your controller.
$scope.data = {};
// OR
$scope.data = {inputStatus=''};
Then in your template
<textarea type="text" ng-model="data.inputStatus" class="statusPostBox" placeholder="what's new on your mind?"></textarea>
Process it in your controller by referring it as $scope.data.inputStatus.
Some people even argue you are doing it wrong if you don't do this for any ng-model, but I find thinking wrapper object name is hard so I still use "dotless" one if I know the there is no child scope.