AngularJS. Filters - angularjs

Trying to understand filers in AngularJS. I have the following code seeding articles from JSON to the home page.
<div ng-repeat='article in articlesList | filter:filters'>
<span>{{article.category}}</span>
<h1>{{article.title}}</h1>
<p>{{article.short_desc}}</p>
</div>
I also have navigation, which I want to act as a filter, so instead of going to a new page, it will just filter article out from home page by category.
<ul>
<li>
<a ng-click="filters.category = 'home'">Home</a>
</li>
<li>
<a ng-click="filters.category = 'activity'">Home</a>
</li>
</ul>
My JSON looks like this
{
"article1": {
"id":"1",
"category": "home",
"title": "Lorem ipsum dolor sit amet, consectetur adipisicing elit.",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img1.jpg"
]
},
"article2": {
"id":"2",
"category": "activity",
"title": "Lorem ipsum dolor sit amet, consectetur adipisicing elit.",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img2.jpg"
]
},
}
I am fetching data from it with this controller
app.controller('HomeCtrl', function($scope, $http) {
$http.get('pages/articles.json')
.success(function(data) {
$scope.filters = {}
$scope.articlesList = data;
})
.error(function(data) {
alert('Something is wrong with JSON')
})
});
I am trying to figure out the right way to set up the Filter. Looking for a solution or some article dealing with similar problem.
Or maybe filtering is not the best option for this task. I appreciate any suggestions/best practices.

I kept making edits to my comment, so I figured I would just make an answer to the question.
In order to use your filter, you're going to need to do something like this:
<div ng-repeat='article in articlesList | filter:categoryFilter'>
<span>{{article.category}}</span>
<h1>{{article.title}}</h1>
<p>{{article.short_desc}}</p>
</div>
And then, in your navigation bar (assuming they have the same scope, if not then the answer would be mildly different), you would do something akin to this:
<ul>
<li>
Home
</li>
<li>
Press
</li>
</ul>
I don't have any data to test this out thoroughly. If it doesn't work, please provide a few (~5) objects in the JSON array so that I can test it out on a plunkr. Also the HTML in your body tag would help greatly.
Here is the API documentation for filters, with a few examples that may help you understand filters a little more.
EDIT
Since you updated your information, I got This Plunkr working.
I'll break each part down:
HTML
<!DOCTYPE html>
<html ng-app="home">
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="dataCtrl">
<nav>
<a ng-click="categoryFilter.category='Home'">Home</a>
<a ng-click="categoryFilter.category='Activity'">Activity</a>
<a ng-click="resetCategoryFilter()">Reset</a>
<span style="float:right;"><input type="text" ng-model="categoryFilter.title"></span>
</nav>
Your Current Filters Applied Are: {{categoryFilter}}
<div ng-repeat="item in data | filter:categoryFilter">
{{item.title}}
</div>
</body>
</html>
Now for the HTML, you'll see that I have just 1 controller being set. This makes it so that all of these items are in the same parent. If you have a wrapper DIV, I suggest putting the ng-controller on that div.
You'll notice that I didn't put the elements in an <ul> or <li> tags. That's because I forgot. It should still work inside them.
That being said, You'll notice my code uses the $scope variable throughout. You seem to have a basic working knowledge of the $scope variable, so I'll leave it mostly alone. The one thing is the function I added to the scope, the resetCategoryFilters() function. It's a simple enough function: it takes all of the keys and sets their values to undefined so that the filters object is reset to base. I initialized the categoryFilters in the scope because... well I'm not sure, it just bothers me if I don't.
Javascript
// Code goes here
var app = angular.module('home', []);
app.controller('dataCtrl',function($scope){
$scope.categoryFilter = {
category:undefined,
title:undefined,
short_desc:undefined
};
$scope.resetCategoryFilter = function(){
for(var x in $scope.categoryFilter){
$scope.categoryFilter[x]=undefined;
}
}
$scope.data = [
{
"id":"1",
"category": "Home",
"title": "20 Useful Things around your House",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img1.jpg"
]
},
{
"id":"2",
"category": "Activity",
"title": "Carpe the Diem",
"short_desc": "Nemo enim ipsam voluptatem quia voluptas sit",
"images": [
"img/article-img2.jpg"
]
}
];//Here, instead of hard-coding the data like I did, use the $http service to get the values
});
You'll notice where I simply hard-coded the data. Since I was using Plunkr, I could have set that data off in a different page, but it was just easier to have it there. Use the $http service and the .get method to populate the $scope.data array.
Remarks About Your Javascript
I noticed that the indexes in the JSON you were giving were hard coded. I deleted them, making the data object an Array of objects, as opposed to an object of objects. Easy to loop through an array, difficult to loop through an object.
Hopefully this answers your question.

Related

Error using react-read-more-read-less with data from a dabase

I'm working on a website with react that maps through an instructors table and displays the instructors' image, name, and bio. I'm trying to use the react-read-more-read-less component to initially show 300 characters then show a Read More link to display all the text. If I hard code the text in the component it works perfectly but when I try to pull the data in from the db, I get an error that children.substr is not a function.
Here is the code:
<ReactReadMoreReadLess
readMoreClassName="readMoreClassName"
charLimit={300}
readMoreText="Read More"
readLessText="Read Less"
>
{parse(inst.instructorBio)}
</ReactReadMoreReadLess>
If I just use {inst.instructorBio} it works find but displays the p tags p.
I even tried to write a toggleShow function and it worked but it expanded all the instructors bios and I wasn't able to figure out how to only expand the one clicked.
Pares is from a npm package I installed to render the data correctly without showing the p tags. The pagckage is html-react-parser.
First solution:
The error occurs because only text is expected as a child element. Only text has a substring (substr) feature.
My solution will require further refinement but may be a good start. Use replacement for paragraphs. Maybe this function is enough:
const convert = (html) => html.replace(/<p>/gi, "\n").replace(/<\/p>/gi, "");
In order to use \n we need to wrap the element in pre
return (
<pre>
<ReactReadMoreReadLess charLimit={5}>
{inst.instructorBio}
</ReactReadMoreReadLess>
</pre>
);
This solution preserves line breaks well, but for the rest you will have to work with styles to make lines behave like paragraphs.
For wrap text in pre:
pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
Second solution for many paragraphs — own component:
The -webkit-line-clamp CSS property allows limiting of the contents of a block to the specified number of lines.
const Fragment = React.Fragment
const MoreLessText = ({children}) => {
const [more, setMore] = React.useState(false)
return(
<Fragment>
<div className={"text" + (more ? '' : ' less')} >
{children}
</div>
<span onClick={() => setMore(!more)}> {/* toggle state*/}
{ more ? 'Show Less' : 'Show More'}
</span>
</Fragment>
)
}
const App = () => {
return (
<table border="1">
<tbody>
<tr>
<td>
<MoreLessText>
<p>
1) Lorem ipsum dolor sit amet consectetur adipisicing elit. Labore fugiat cumque, aliquam minus repellat voluptates adipisci. Consectetur, voluptatem nam quasi alias minima, nihil tenetur odit, a atque deleniti maiores est?
</p>
</MoreLessText>
</td>
</tr>
<tr>
<td>
<MoreLessText>
<p>
2) Lorem ipsum dolor sit amet consectetur adipisicing elit. Labore fugiat cumque, aliquam minus repellat voluptates adipisci. Consectetur, voluptatem nam quasi alias minima, nihil tenetur odit, a atque deleniti maiores est?
</p>
</MoreLessText>
</td>
</tr>
</tbody>
</table>
)
}
ReactDOM.render(<App/>, document.getElementById("root"));
.text{
width: 300px;
}
.less {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin ></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin ></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root">

Child Component to Parent Component Communication without a Children Nested in a Template in Angular (Angular 2+)

Every answered post, and every tutorial shows that the EventEmitter() can be used for a child component to talk to it's parent if the parent is listening; however, that only works if the child component is part of the parent's template; otherwise, the emit can only be picked up by the app's root component.
Here's a simple structure (using <ng-content></ng-content> on the radio-group to transclude the children):
<radio-group>
<radio></radio>
<radio></radio>
<radio></radio>
</radio-group>
When a custom radio component gets checked, it should emit an event--that works great. But the custom radio group won't hear the event even if it's listening because it's children are not part of it's template in this implementation.
I don't understand how Angular Material 2 Radios pull this off; but they'd be a good reference for someone who understands Angular 2 well.
Click on the "Examples" tab of Angular's Material 2 Radio Demo to see what I'm trying to accomplish, and the source button to see the same structure I'm trying to duplicate.
Again, I'm not trying to copy and paste their implementation, I'm trying to get the child components to communicate events to a parent component when the child components are not part of the parent's template.
If you go to that plunker you'll see that they have the following structure:
<radio-ng-model-example>
<md-radio-group class="example-radio-group" [(ngModel)]="favoriteSeason">
<md-radio-button class="example-radio-button" *ngFor="let season of seasons" [value]="season">
{{season}}
</md-radio-button>
</md-radio-group>
<div class="example-selected-value">Your favorite season is: {{favoriteSeason}}</div>
</radio-ng-model-example>
With radio-ng-model-example being the root component. So they use EventEmitter to communicate between md-radio-button and md-radio-group, and ngModel to communicate between md-radio-group and radio-ng-model-example.
When child components are not part of the parent's template, they are basically content children. And they are scoped within Parent component.
So for example, if you have a component named my-component and in its template you have something like below,
<parent [input]='somevar1' (output)='somemethod1()' >
<child [input]='somevar2' (output)='somemethod2()' ></child>
</parent>
somevar1,somevar2,somemethod1,somemethod2 comes from scope of my-component.
Based upon this understanding it is pretty safe to say that the parent and child can be treated as siblings within the scope of my-component. So you can communicate between these two using my-component scope.
Example,
#Component({
selector: 'parent',
template: `<h1>Parent</h1>
{{awesomeInput}}
<hr />
<ng-content></ng-content>
`
})
export class ParentComponent {
#Input() awesomeInput: string;
}
Child component,
#Component({
selector: 'child',
template: `<h1>Child</h1>
<button (click)='sendAwesomeOutput()'>Click me!!</button>
`
})
export class ChildComponent {
#Output() awesomeOutput = new EventEmitter();
sendAwesomeOutput(){
this.awesomeOutput.next('awesome output from child!!');
}
}
Component in which above components are used in template.
#Component({
selector: 'my-app',
template: `<h1>Hello {{name}}</h1>
<hr />
<parent [awesomeInput]="defaultInput" >
<child (awesomeOutput)="getOutputFromChild($event)" ></child>
</parent>
`
})
export class AppComponent {
name = 'Angular';
defaultInput = 'default value for parent';
getOutputFromChild(val){
this.defaultInput = val;
}
}
Update
You may be little creative how you are passing values between parent-Child, and can totally do in template itself. Updated Plunker!!
<parent awesomeInput="{{child1.awesomeOutput | async }}" >
<child #child1 ></child>
</parent>
The one above considers scenarios if you have to process anything.
Check out this Plunker!!
Hope this helps!!
Answering the title question without using ngModel, bindings, or EventEmitter (since it doesn't work outside of a template), and flexible enough to have any kind of content in each child component, in Angular 2.4, here's another approach for other implementations:
Example:
"Presenters" at a council of Odin (the "Organizer"). Only one presenter can talk (appear) at a time. Who ever presents next, signals to the organizer their intent, and the organizer stops the other presenters from speaking so the new presenter can resume their presentation.
(Demo screenshot minus additional nested content:)
Clicking any button causes all the presenters to hide but the one you just clicked.
Usage:
<organizer>
<presenter>
<h2>Thor</h2>
<summary>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt ea nisi impedit aspernatur sint voluptatum odit aperiam, soluta, explicabo, nesciunt earum quo. Vel quod, ratione ullam deserunt commodi quaerat fugiat!</summary>
<asgardian-power-point></asgardian-power-point>
</presenter>
<presenter>
<h2>Loki</h2>
<summary>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt ea nisi impedit aspernatur sint voluptatum odit aperiam, soluta, explicabo, nesciunt earum quo. Vel quod, ratione ullam deserunt commodi quaerat fugiat!</summary>
<interjection></interjection>
<lies></lies>
</presenter>
<presenter>
<h2>Sif</h2>
<summary>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Incidunt ea nisi impedit aspernatur sint voluptatum odit aperiam, soluta, explicabo, nesciunt earum quo. Vel quod, ratione ullam deserunt commodi quaerat fugiat!</summary>
<asgardian-power-point></asgardian-power-point>
</presenter>
</organizer>
organizer.component.html:
<ng-content></ng-content>
organizer.component.ts:
import {
Component,
ContentChildren,
QueryList
} from '#angular/core';
import { PresenterComponent } from '../presenter/presenter.component';
#Component({
selector: 'organizer',
templateUrl: './organizer.component.html',
styleUrls: ['./organizer.component.scss']
})
export class OrganizerComponent {
#ContentChildren(PresenterComponent) presenters: QueryList<PresenterComponent>;
stopPresenters() {
this.presenters.forEach(function(item) {
item.conclude();
});
}
}
presenter.component.html:
<div style="overflow:hidden;" [style.height]="expand ? 'auto' : '30px'">
<button (click)="present()">Present</button>
<ng-content></ng-content>
</div>
presenter.component.ts:
import {
Component,
Input,
Inject,
forwardRef
} from '#angular/core';
import { OrganizerComponent } from '../organizer/organizer.component';
#Component({
selector: 'presenter',
templateUrl: './presenter.component.html',
styleUrls: ['./presenter.component.scss']
})
export class PresenterComponent {
constructor(#Inject(forwardRef(() => OrganizerComponent)) private organizer:OrganizerComponent) {}
#Input() expand: boolean = false;
present() {
this.organizer.stopPresenters();
this.expand = true;
}
conclude() {
this.expand = false;
}
}

AngularJS: Create tab links by creating the tab's content in the DOM

I'm new to AngularJS,
Sorry if this is a silly question.
I googled quite a lot and I can't find existing help online.
Basically, I'm trying to generate the tab headers dynamically by creating the content sections.
I have no idea how to go about it.
Simply pointing me in the right direction would be a lot of help!
I want to implement in my code something like this
<div ng-show="panel[this].isSelected" ng-attr-title="JS">
lorem ipsum...
</div>
<div ng-show="panel[this].isSelected" ng-attr-title="C#">
lorem ipsum...
</div>
<div ng-show="panel[this].isSelected" ng-attr-title="Node">
lorem ipsum...
</div>
And generate the tabs using ng-repeat automatically.
Expected output:
JS | C# | Node
Any tips on where to begin?
<div ng-repeat="panel in panels" ng-class="{'selected': panel.selected}" ng-attr-title="{{panel.title}}">
lorem ipsum...
</div>
<script>
//...
$scope.panels = [{'title': 'JS', selected: false}, {'title': 'C#', selected: false}, {'title': 'Node', selected: true}]
//...
<script>
you probably need this, check the api doc ng-repeat and ng-class

How to reuse controller for different views in AngularJS?

I am receiving object data from one JSON request, similar to this:
{
"slides": [
{
"image": "http://lorempizza.com/380/240",
"title": "Slide aa",
"description": "I owe you the description"
},{
"image": "http://lorempizza.com/380/240",
"title": "Slide bb",
"description": "I owe you the description"
}
],
"title": "Interesting object",
"description": "Lorem ipsum dolor sit amet consectetur."
}
I want to use two different views. One for the slides and another one for the title and description of the object, everything on the same page. Why? Because I'm using ui views to display them.
index.html
<div ui-view="slider"></div><!-- Here is the slider -->
<main ui-view role="main"></main><!-- Here is title and desc -->
The problem is here:
slider.html
<div ng-controller="InterestingObjectCtrl">
<!-- Display slider -->
</div>
main.html
<div ng-controller="InterestingObjectCtrl">
<!-- Display title and desc -->
</div>
InterestingObjectCtrl.js
InterestingService.get()
.success(function(data) {
$timeout(function() {
$scope.$apply(function() {
$scope.interestingObject = data;
});
});
})
The InterestingObjectCtrl controller will load twice the same JSON making useless HTTP requests.
What would be the right way or "angular way" to solve this?
Setting a flag (like if ($scope.requestAlreadyMade) return;) will definitely won't work.
The HTTP requests may not be a big deal caching the $http service, but that is far from the point of this problem.
Calling the controller as a wrapper, like the following example, will make it be loaded from every page on the website, which is even worst.
<div ng-controller="InterestingObjectCtrl">
<div ui-view="slider"></div><!-- Here is the slider -->
<main ui-view role="main"></main><!-- Here is title and desc -->
</div>
<div ng-controller="MainCtrl">
<div ui-view="slider"></div><!-- Here is the slider -->
<main ui-view role="main"></main><!-- Here is title and desc -->
</div>
load data once in MainCtrl and breadcast it to child controllers:
InterestingService.get()
.success(function(data) {
$scope.$broadcast('dataloaded', data);
});
InterestingObjectCtrl.js
$scope.$on('dataloaded', function (data) {
// do what you want
});

Bootstrap scrollspy and backbone routing

How would i go about using Bootstrap's Scrollspy when I am using Backbone.js hash based routing?
Backbone router example, which creates page www.example.com/#somePage/123
var AppRouter = Backbone.Router.extend({
routes: {
"": "home",
"somePage/:id": "somePage"
},
somePage: function (id) {
console.log("do something");
}
});
$(document).ready(function(){
window.app = new AppRouter();
Backbone.history.start();
});
Twitter scrollSpy example which should append #anchor-value to the end the URL:
<div id="navbar" class="row-fluid">
<ul class="nav nav-pills navbar">
<li class="active">
1
</li>
<li>
2
</li>
</ul>
</div>
<div data-spy="scroll" data-target=".navbar">
<h4 id="step1">Step 1</h4>
<p>Ad leggings keytar, brunch id art party dolor labore. Pitchfork yr enim lo-fi before they sold out qui. Tumblr farm-to-table bicycle rights whatever. Anim keffiyeh carles cardigan. Velit seitan mcsweeney's photo booth 3 wolf moon irure. Cosby sweater lomo jean shorts, williamsburg hoodie minim qui you probably haven't heard of them et cardigan trust fund culpa biodiesel wes anderson aesthetic. Nihil tattooed accusamus, cred irony biodiesel keffiyeh artisan ullamco consequat.</p>
<h4 id="step2">Step 2</h4>
<p>Veniam marfa mustache skateboard, adipisicing fugiat velit pitchfork beard. Freegan beard aliqua cupidatat mcsweeney's vero. Cupidatat four loko nisi, ea helvetica nulla carles. Tattooed cosby sweater food truck, mcsweeney's quis non freegan vinyl. Lo-fi wes anderson +1 sartorial. Carles non aesthetic exercitation quis gentrify. Brooklyn adipisicing craft beer vice keytar deserunt.</p>
</div>
This wants to turn the URL to something like www.example.com/#somePage/123#step1, which is not working.
Here is a possible solution using the Bootstrap demo Scrollspy: https://jsfiddle.net/8wvdpddq/
Assuming you wish to have the URL updated and a history point added as the user scrolls, the following code should achieve it:
$('body').on('activate.bs.scrollspy', function () {
var active = $('nav li:not(.dropdown).active a').attr('href').slice(1);
window.app.navigate(active, {trigger: true});
console.log('update url/history to ' + active);
})
In this case, trigger is also set, meaning your routing handlers will fire, if you don't want this, just remove this option.

Resources