text:
Columbus' Ship Pattern
Build a ship with three masts. Color, cut out, and assemble the pieces.
Subject: Transportation Grade: PreK - 5 Theme: Autumn Themes, Transportation, Columbus Day Type: Learning Activities and Worksheets Author: Karen Sevaly File Size: 154 KB
Subscribe Now
and get unlimited access to 20,000+ downloadable resources
Already a Subscriber? Click here to sign in


text:
Plus Size Clothing2689492
Discover a plus-size person, may very well not have ever used cheap plus size clothing online. In fact, you might not be also knowledgeable about the word. In case you might be a bigger average or "plus-size" individual, the definition of will need to have become a part of your shopping vocabulary.
What is Plus-Size Clothing?
Plus-size clothes are clothing relating to the numerical sizes of 14W and upwards to size 26 (the W means women). Also referred to as X sizes (1X, 2X, etc), that have numerical size equivalents (check the tag). It's also possible to hear or see plus-size clothing generically called "Women's Sizes." In lots of major stores there is a separate section for plus-size clothing.
Difficulties to find Larger Sizes
Even though the average woman may wear a size 12 - we're not all average sized women. Some of us are smaller in stature and tend to shop from the petite or juniors part of the most popular variety store. For those who are greater than average, plus-size clothing could be their only avenue for fashion.
For quite some time, ladies who wear cheap plus size clothing online have had difficulties to find their sizes in regular retail stores or whatsoever. When able to uncover their size, younger women have discovered it even more difficult to get fashionable clothing in plus-sizes. Now, however, due to the rise in the plus-size clothing market, many countries are seeing more specialty stores who carry lines specifically targeted to plus-size women.
Specialty Retailers
Probably the most recognizable stores in america is Lane Bryant, who provides a selection of styles including business, casual, and dress clothing for bigger women. Lane Bryant practices another major feature of plus-size clothing shopping - the convenience of shopping on the web and teleshopping catalogs.
The specialty store Torrid specifically targets younger plus much more style aware plus-size women. Just like Lane Bryant inside the types of clothing offered, Torrid is much more concentrated with young women and teenagers.
Internet Shopping
The world wide web has opened the plus-size clothing industry for many consumers. Women are able to browse stores and brands that are far outside their physical location. When you shop for clothing online does not accommodate trying on the clothing before purchase, the ready accessibility to a broader numerical size range can be a big draw for plus-size women. Shoppers can observe styles, comparison shop and be related to similar stores. The world wide web offers something any local mall will not in connection with plus-size clothing - plenty of selection. Simply enter in the keyword plus-size clothing to your search results, and you'll find countless sites that will offer only the type of clothing you are looking for within your size.

subset: commoncrawl


text:
CentraState Healthcare System's marketing team is currently seeking a Digital Marcom Specialist to work 30 hours/4 days per week. The hours are 8:30am to 4:30pm or 9am to 5pm. This position is eligible for medical, dental, vision, prescription and PTO benefits. The Digital Marcom Specialist will be responsible for Web Content Maintenance, SEO/SEM management and our e-newsletter. The Digital Marcom Specialist must have a Bachelor's degree in marketing or related field, great written communication skills and prior marketing or advertising experience with healthcare exposure a plus. The Digital Marcom Specialist will be tasked with keeping up with the ever changing landscape of digital marketing.
Works collaboratively with the public relations (PR)/marketing team, agencies and other partners to optimize online patient acquisition by:


text:
Home › Forums › Campfire › Post-Trip Reports › Point Reyes National Seashore – First TR
Jul 12, 2011 at 4:22 pm #1276629
Bradley AttawayMember
@attaboybradLocale: San Francisco Bay Area
A trip from a few weeks ago. It was my friend Ed's first time camping and my first lightweight trip (though, as you'll see, some of the lightweight part was accidental). In fact, it was following this trip that I went UL for serious and Ed went as UL as his girlfriend would allow.
We arrived at Bear Valley visitors center around 4pm on a Tuesday, having no idea that campsites fill up each night, even at midweek. A Ranger dashed our hopes of camping by the beach and told us that only one site remained for the night, at Glen Campground, 2 miles inland. As it happened, this was a lucky break…
It turned out that I had left both poles and fly for my REI Quarter Dome (which I had, up to that point, thought was insanely light at just 4lbs for the plus model) at home. As it happened, there were enough branches and anchor points around to jerry rig the inner wall into a bug shelter and bathtub floor, at the very least, and the forecast suggested no precipitation was imminent.
Our makeshift camp now pitched, we backtracked to the trail down to Wildcat Beach, and found this lovely view along the way…
This plant grew in pockets everywhere. As a recent East Coast transplant, I'd never seen it before, and when they were looked at from the right angle en masse, they seemed to mimic a Gaussian blur, which I thought was pretty cool…
After a total of less than 10 miles, Ed's overloaded pack had already rubbed his hips raw. This was his first time camping, but he loved to cook, and thought it perfectly reasonable to bring whole cans of beans and tomatoes, jars of spices, utensils, etc. for our chili dinner. His pack must have weighed near 40lbs for about 24 hours on trail. We forgot our pain once we hit the coast…
Ed promptly set to dinner, and though I doubt he'll bring so much weight in food next time, I was grateful for the feast he prepared…
We had hoped to cook on coals after having a proper campfire, but scouring the beach (the only place from which we were allowed to collect wood) turned up only enough small branches for a token campfire.
The tide came in while we were eating and we had to climb around on the cliffs behind us to get back to the path into the beach. Unfortunately our hands were too occupied with holding on to grab a photo.
We were blessed with a full moon for the hike back to camp from the beach…
All of these photos were taken by me using Ed's Nikon D3100 and 18-55 kit lens. Light was either found or provided by headlamps. Post processing of JPGs was done on the ride home on my iPad 2 with PhotoForge 2 and Snapseed by Nik Software.Jul 13, 2011 at 8:31 am #1758836
Adam HemphillMember
@ahemphillLocale: Bay Area
Nice report! I know the spot pictured just after the plant. I'm hoping to make it up to Point Reyes before the season is out; the last time I backpacked up there was a few years ago. (We got Sky Camp and it was amazing.)Jul 13, 2011 at 9:27 am #1758848
Dave TMember
Nice quick hike. 40 pounds for 24 hours! Yikes! I did a 4 days there out of a Golite Ion a couple years ago, and had a great time. The plant is a horsetail (Equisetum) – they are very cool looking.Jul 17, 2011 at 7:32 pm #1760316
Justin BakerBPL Member
@justin_bakerLocale: Santa Rosa, CA
Nice trip. I went there last fall to wildcat camp and we all hung out at the same beach next to a campfire. Probably until 2 in the morning. We probably burnt every piece of wood within a quarter of a mile. Sorry…..lol.
We got in when it was dark, I didn't see the bear box, left my food on the table, came back and it was gone. Stupid raccoons….
There was so much moisture in the air that my sleeping bag got wet and all of the clothing I was going to sleep in, which was placed on the table, was soaked. Very miserable night. If I wasn't on a barren coastal bluff and I was in a woodland with firewood around, I would have burned it, regardless of regulations. That's how cold I was.
On the way out we went by the waterfalls.
You must be logged in to reply to this topic.


text:
One-piece, precision machined Weaver rail lets you mount scopes, red-dot sights, ghost-ring sights, and other optics using Weaver-style rings. Plenty of cross-slots allow optimal ring spacing for a wide range of scopes including compact, full-size, and extra-long scope tubes. Low-profile, open center design allows use of front sight when optics are removed. Lightweight aluminum reduces weight without compromising strength. Hardcoat anodized for additional strength and wear resistance. Attaches to factory drilled and tapped receivers using the included steel mounting screws.
SPECS: Aluminum, anodized finish, matte black. Steel mounting screws. Short Action (SA) - 57⁄8" (14.9cm) long. Long Action (LA) - 6¼" (15.8cm) long. Fits Rem 700, 78, 40X-L, 721, and 725. Accepts Weaver-style rings.
Mfr Part: 48330
Color: Black
Material: Aluminum
Number of Bases: 1-Piece
Style: Weaver-Style Bases
Mfr Part: 48334
Would you like to be alerted when the item becomes available again?
Please enter your email address and quantity below andwe will send you an alert when the item becomes available.
Please note that alerted inventory is on a first come, first serve basis.
Thank you for contacting our Chat Team! Please visit us again.
If you would like a copy of the chat transcript please click "Send Transcript" below.
Hello, to begin a chat session with a customer service representative, complete the fields below and click Chat


text:
Total Commitment (6 of 12) by Jim Perdue
This content is part of a series.Total Commitment (6 of 12)
Series: Breakthrough
Jim Perdue
Joshua 5:1-15
Let's take a moment and review what's been happening in the book of Joshua. Moses, the great leader of Israel, was now dead. Joshua was the new leader. He challenged the people to step out and claim the victory in Christ; as a result, the people followed the voice of God and walked through the Jordan River on dry ground. They left the wilderness and entered the Promised Land. God instructed the people to erect a monument that would be a sign for all time of God's miraculous power, presence and provision.
And now, the nation of Israel finds themselves in the Promised Land for the very first time. They are stationed in Gilgal, ready to claim the promise of God. But instead of moving forward, God has instructed them to wait. God desired His people to be prepared; He would accept nothing less than total commitment. READ 1-15
*As I read this passage, it reminded me of a scene in the movie Miracle. This is the story of the 1980 US Olympic Hockey Team that won the gold medal by improbably defeating the Soviet Union in the Olympics. As the United States defeated the Soviet hockey team 4-3, the announcer Al Michaels gives his famous exclamation, ''Do you believe in miracles?! Yes!!!'' Many of you know the story, but what you may not know is what happened before the game; before the battle. Leading up to the Olympics, the USA hockey team had just tied a very poor Norwegian hockey team. Their coach was less than pleased. Take a look and see what happened…***VIDEO CLIP***
I want nothing less for my life and for our church than complete and total victory. Complete and total victory takes complete and total commitment. You're either in or you're out; there's no halfway when it comes to serving God. It takes total commitment. Pay the price, make the call, take the shot, give it your all…it's worth it!
I'm not satisfied sitting in the stands or standing on the sidelines ...
There are 12747 characters in the full content. This excerpt only shows a 2000 character sample of the full content.
Price: $4.99 or 1 credit
Sign up for a Free Trial with SermonSearch.com and download this sermon free today!
Featured Worship Backgrounds


text:
It's not a regular occurrence that you realize that you have to buy a replacement Oil Filter to repair your automobile. You picked your Crosstrek because its versatility and a fun driving experience appealed to your senses; so why would you be okay with second best when it comes to the highest quality auto parts? Anyone who operates a Subaru knows that when it comes to repairing their vehicle only the most superior replacement parts will do. When your Subaru Crosstrek was born, it was destined for a person with discerning taste who prioritizes youthfulness in their automobile.
The thousands upon thousands of revolutions our engines make each day would eventually tear them apart without lubrication. The Purolater (the first oil filter) was invented in 1923, and your Subaru Crosstrek engine still uses the technology that is derived from this early invention. Your Subaru Crosstrek oil filter traps the dirt and metal shavings generated by the engine as oil flows through it so it remains clean. If you didn't have an oil filter, the engine oil would become increasingly dirty, highly viscous, and ineffective.
Don't let a broken Oil Filter bring about the demise of your car, truck, or SUV, and don't gamble with its condition by using anything but the best repair parts. Get your Crosstrek back on the road with the best replacement parts from the experts at Car Parts Discount. Given all the hassles associated with fixing broken parts on your car or truck, let www.carpartsdiscount.com be the first place you go to make buying it easy. Need 2017, 2016 Subaru Crosstrek Oil Filter parts? We've got them right here.


text:
Originally posted on FOX Sports | Last updated 3/2/13
Reversing the result of their semifinal here a year ago, John Isner on Friday served his way past defending champion Kevin Anderson 6-2, 7-6 on a chilly night in the quarterfinals of the Delray Beach International. It was a morale-boosting win for the 6-foot-9 American, whose confidence does not always match his stature on a tennis court. Forced to miss the Australian Open with a knee injury, Isner lost in the first round last week at Memphis following a semifinal showing at San Jose, and now seems to be hitting his stride in testing conditions. With the weather hot, humid and windy early in the week before rain washed out Wednesday's play, Isner had to adapt to temperatures that had plunged into the 50s. The chill did not help Anderson, a U.S.-based South African who underwent elbow surgery in Melbourne at the end of January and needed time to warm up. Before he did, the first set was gone in a blur of Isner thunderbolts that included five aces. Twelve more followed in a protr...
Is Serena Williams the most important modern athlete?
Tennis News
Delivered to your inbox
You'll also receive Yardbarker's daily Top 10, featuring the best sports stories from around the web. Customize your newsletter to get articles on your favorite sports and teams. And the best part? It's free!
Follow us on Twitter
|Best of Yardbarker
|NFL News
|MLB News
|NBA News
|NHL News
Today's Best Stuff
For Publishers
Company Info
Follow Yardbarker
|© 2017 Yardbarker, Inc. All rights reserved. Use of this website (including any and all parts and components) constitutes your acceptance of these Terms of Use and Privacy Policy. (What's New?)


text:
Electric Actuator, -40 to 140F, 100-240V
HONEYWELL| Zoro #: G1190813| Mfr #: MS4110A1200
Zoro #: G1190813Mfr. Part #: MS4110A1200
$205.47 /ea
This item ships free. DetailsStandard ground shipping
Technical Specifications
|Zoro #: G1190813 Mfr #: MS4110A1200
|Timing: 45 sec. Nominal @ 60 Hz, 20 sec. Spring Return
|Includes: Mounting Bracket, 2 Internal Auxiliary Switches, Self-Centering Shaft Adapter, 3mm Crank
|Shaft Type: Self-Centering Clamping
|Stroke Deg.: 95 +/-3
|Torque: 88 in.-lb.
|Item: Electric Actuator
|Spring Return: Yes
|Voltage: 100-240VAC
|Height: 9-3/4"
|Switch Type: 2 SPDT
|Electrical Connections: Enclosed Screw Terminal Strip
|Signal Type: On/Off
|Depth: 2-15/16"
|Power Consumption: 45VA Driving, 13VA Holding
|Mounting Type: Direct Mount
|Ambient Temp. Range: -40 Degrees to 140 Degrees F
|Width: 3-15/16"
|Country of Origin (subject to change): Mexico
Product Description
Actuator, Spring Return 2 Position, Shaft Type Self-Centering Clamping, Torque 88 In-Lbs, 95 +/- 3 Stroke Deg, Height 9 3/4 In, Width 3 15/16 In, Depth 2 15/16 In, Electrical Connections Enclosed Screw Terminal Strip, Signal Type SPST, Ambient Temp. Range -40 to 140 F, Power Consumption 45VA, Timing 90 Sec, Voltage 100-250 VAC, For Use With Valves And Dampers, Includes Mounting Bracket, 2 Internal Auxiliary Switches


text:
Welcome to WebmasterWorld Guest from 184.108.40.206
Forum Moderators: incrediBILL & lawman
Thousands of office printers around the world have been spewing out page after page of gibberish because of a computer virus.
Reports from companies reveal that thousands of pages of paper were wasted when the Windows virus hit their PCs.
Security firms said the worst hit were large businesses in the US, India, Europe, and South America.
The culprit is a malicious program called Milicenso that has been re-used many times by hi-tech crime groups.


text:
Superaggregator of any and all payment modes, online or offline. Offers largest chain of OTC collection outlets and banks (2.75 lakhs by August). Clients include IISc Bangalore, Council of Architecture
Dynamic form builder for all types of collection situations like fees, bills, loans, municipal collections, donations etc. Integrated solution with SabPaisa available. Clients include CHSE Odissa, NIT Jalandhar.
Dynamically generate and share personalized links to enable collections. Integrated solution with SabPaisa available. With pre-fill feature, you can process and pre-fill any number of data points from a database.
Initiate multiple settlements to multiple beneficiaries Availability of multiple modes like NEFT/RTGS, Fund Transfer, IMPS Validation and payment through Aadhar enabled PS available.


text:
Safeway Canada has a few great offers this week. Check out the list below and remember to print these coupons or take your mobile device to show the cashier.
These coupons are valid for a limited time and expire April 3, 2014!
- Eating Right Frozen Boneless Skinless Chicken Breasts $10.99
- Open Nature Hummus 260g $1.99
- Eating Right Ancient Grain Bread $3.49


text:
Where the Massachusetts Insurers Insolvency Fund is the successor to an insurance company that issued one of several policies to a physician subsequently held liable by a jury for medical malpractice, the judgment, including prejudgment interest, must be apportioned between ...Read More »
Contract – Mechanic's lien – Scrivener's affidavit
Where a defendant real property owner has moved to dismiss a plaintiff's complaint asserting a mechanic's lien, the dismissal motion must be allowed because the attempted recording of the lien failed to comply with G.L.c. 254, §4. "[Plaintiff] United Rentals ...
Tagged with: mechanic's lienRead More »
Attorneys – Fees – Non-clients – Fiduciary duty
Where (1) a plaintiff law firm that represented a client in a divorce proceeding brought suit to recover legal fees from the client's parents, who signed the fee agreement a guarantors, and (2) the parents have asserted counterclaims, a counterclaim ...Read More »
Civil practice – Default – Preliminary injunction
Where defendants were defaulted on some of the issues in some of the counts of the plaintiff's complaint, the plaintiff's request for entry of a default on all counts must be denied. "I decline the request of the Plaintiff to ...Read More »
Civil practice – Default – Spoliation – Fraud upon the court
Where defendants (1) violated a preliminary injunction issued by selling products covered by the preliminary injunction, (2) spoliated evidence and (3) committed fraud upon the court, the defendants should be defaulted as to some of the issues in some of ...
Tagged with: Default SpoliationRead More »
Tort – Defamation – Privacy
Where a defendant newspaper has moved to dismiss a complaint filed by two plaintiffs whose picture was published in connection with an article about the 2013 Boston Marathon bombing, the motion should be denied as to counts of defamation and ...
Tagged with: DefamationRead More »
Attorneys – Fee award – Retaliation suit
Where a plaintiff prevailed on a claim against the defendant employer for retaliation, she should be awarded counsel fees of $215,204 plus costs in the amount of $6,673.70. "Plaintiff's attorney, Robert Mantell, seeks compensation at an hourly rate of $425. ...Read More »
Employment – Retaliation – Transfer – Emotional distress
Where a defendant employer moved for judgment notwithstanding the verdict after a jury found for the plaintiff employee on a retaliation claim and awarded her $139,000 in back pay, $500,000 in emotional distress and $250,000 in punitive damages, the motion ...
Tagged with: Emotional distressRead More »
Attorneys – G.L.c. 93A – Fees
Where a defendant attorney has been held liable under G.L.c. 93A for charging a plaintiff client an excessive fee, the plaintiff should be awarded a total of $135,379.91 in counsel fees and costs. "… At trial, a jury decided that ...Read More »
Contract – Debarment – Fine
Where a contractor has been debarred from bidding and contracting to construct public works projects, the contractor timely paid a $500 fine, so a preliminary injunction in its favor is appropriate. "The plaintiffs, Lighthouse Masonry, Inc. and its president, Paul ...Read More »


text:
Since this demands a riff of some sort:
- Tree-sus, thank you Mr. Reporter, isn't bad.
- The Tree of Life?
- The Joshua Tree?
- The Tree of the Fruit of the Knowledge of Good and Jesus?
- I learned about panentheism in school, but I didn't think it looked like this.
--By the way, I love how this is inspiring our lumberjack to go to Church more often. I'll be the very first to grant the Lord works in mysterious ways, but I want to be there for that altar call.
Help for Tori continues. There's been a lot of good out of this--good conversations and conscience-raising around town, people working on this benefit together who normally don't collaborate, etc. And between two bloggers, I believe we're at $4000. But she really needs much more. Please consider it. Thanks.


text:
Flap their wings
Before nesting
On her lips.
The beauty of
Less revealed more concealed
Like tender breast of a Marathi lady
In the cave of her mind.
Do enter often silently
In the yard of her heart
Like intruders
Cross the border of another Country.
Words come and leave,
Sleep and dance
Hurry on her lips
She is my aphasic daughter.


text:
mattress stores in Ashburn, VA
Macy's21060 Dulles Town Circle, Dulles, VA map
Macy's, established in 1858, is the Great American Department Store - an iconic retailing brand over 740 ... more
- more info
- |
- Products And Services
Mattress Discounters22000 Dulles Retail Plz Ste 124, Sterling, VA map
Mattress Land22195 Shaw Rd, Sterling, VA map
Mattress Warehouse45591 Dulles Eastern Plz Ste 102, Sterling, VA map
Mattress Firm Dulles North22000 Dulles Retail Plz Suite 124, Dulles, VA map
Mattress Firm features more than 55 different mattresses from top manufacturers such as Beautyrest, Serta... more
Furniture RowServing Ashburn, VA
Home To A Different Kind of Shopping Experience���. Find a location now! more
Mattress Warehouse42015 Village Center Plz Ste 145, Aldie, VA map
Mattress Land1060 Elden St, Herndon, VA map
Mattress Warehouse47100 Community Plz, Sterling, VA map
Mattress Warehouse550 Fort Evans Rd, Leesburg, VA map
Mattress Warehouse491 Elden St, Herndon, VA map
Top Trending Articles
How to Choose the Right Mattress for Your Ind...
There are many different types of mattresses available,... more
How Often You Should Replace Your Mattress?
Having a good mattress is essential for a restful night... more
Keep Your Mattress Clean With These Easy Tips...
Most people don't think of their mattress when they&nbs... more
How to Purchase the Correct Bed For Your Chil...
Read more to find out how you can buy the perfect bed f... more
How to Get Rid of Bed Bugs
Bed bugs are unhygienic and can wreak havoc in your hom... more


text:
Mortice LocksMortice locks are the minimum standard of security that many insurance companies will accept, and they come in either two, three or five lever varieties. Security and cost-wise, only five-lever locks should be fitted, and the lock must conform to BS 3621. Mortice locks can be fitted either with or without a handle. If fitted with a handle, the door has to be unlocked with a key, and then opened with the handle. This is called a "Sashlock".
If fitted without a handle, the door can only be opened using the key; these are called "Deadlocks". Mortice locks are sometimes erroneously referred to as "Chubb Locks"; while Chubb do manufacture them, they are not the only company that do.
With locally based engineers covering all areas, Express Bickley locksmiths specialise in all types of lock services including lock replacement, lock repair, gain entries, safe opening and full lock installations.
Express Locksmiths Bickley pride ourselves as being one of the areas premier 24 hour* emergency locksmith companies. There is never a good time to lose your keys and become locked out. When a problem occurs that requires an immediate response we offer complete lock solutions including gain entry and lock repair throughout Bickley 24 hours a day*, 365 days a year.
Highly skilled Bickley locksmiths are on standby with fully equipped vans containing all the necessary equipment to carry out any lock service.
With an our local Express Bickley locksmiths you know your in safe hands. We have had many recommendations through word of mouth. This is due to our policy of putting customer satisfaction as well as safety, above all else. No payment is taken until the work is completed and you have signed to say you are satisfied! (deposit may be required for parts purchased).


text:
07 May 15
Wendell Scott and Sons
Help support the Wendell Scott Foundation's Steer into STEM After school program by purchasing your t-shirt today!! Buy Wendell... →
View More: Sports News|Live News|More News Videos DAYTONA — As Sunday's Daytona 500 made history, 68-year-old Frank Scott recalled... →
Be sure to check out the CBS Evening News with Michelle Miller when she interviews son and grandson of... →
Copyright © 2010 The Wendell Scott Foundation. All Rights Reserved.


text:
Was £49.95 Now £22.00
Size RangevariationSizeVariation_9
Regular 59
Your favourite Supima® cardigan
A perfect fit and weight, refined look and feel and quality that endures are the hallmarks of our Supima® cardigans, knit from America's best cotton. The extra-long fibres feel smoother, hold dye better and last longer than lesser cotton. The result? Soft, resilient cardigans that drape beautifully and look perfect every time you pull one on. Whether matching a twinset or layering patterns and colour, these are the classically beautiful sweaters that you build your wardrobe around. 100% cotton Supima® . Machine wash.
Approx. length in Regular size M: 61cm/24ins
Approx. length in Regular size M: 61cm/24ins
- All over zigzag pointelle stitch and striped trim gives it a pretty sporty flair
- Incredibly soft fine gauge Supima® cotton
- Fully-fashioned knitting matches seams and ensures a perfect fit
- Ribbed cuffs and hem keep their shape
- Classic crew neckline
- Striped inside placket
- Tubular trim


text:
Is your resume in need of a little disruption?
Startups are all about
making things work better.
Our resume writing service is all about doing that for your CV.
StartUpHire is the place to find Mobile jobs in and near TULALIP. Review the list below of Mobile TULALIP jobs. These Mobile jobs represent some of the most rewarding careers available. You can refine your job search for Mobile jobs in TULALIP by adding a job title, company name, or keyword to the search box above … or click the "Advanced Search" link for more options. Don't see the employment opportunity you're looking for? Check back soon for more Mobile jobs in TULALIP, or sign up for an email alert (link in upper left) to receive periodic updates on new Mobile jobs in TULALIP, WA.
When you?ve been there and done that, come to Xevo! Somehow, you?ve been writing software for over a decade. You?ve seen programming fads come and go. You?ve seen and heard all the stories. Heck, you have quite a few of your own. Now you only want to work with the best. You4/8/2017 Kirkland, WA Save This Job
Department: Engineering Location: Kirkland, WA Job Overview: Our company builds cloud-based content management systems for high end digital signage and rich user experiences for cruise ships, hotels, and motor coaches. As an engineer on the applications team, you'll be4/8/2017 Kirkland, WA Save This Job


text:
|incl. VAT
|excl. VAT
Transporting bulky car often costs? Racks for Renault Megane II, 5-dr Combi (r. v. 2003 -> 2008) are used for simple attachment points located behind the built for this purpose.
Transporting bulky car often costs? Racks for voz gauge Renault Megane II, 5-dr Combi (r. v. 2003 -> 2008) jsou určeny k jednoduchému uchycení za vestavěné body připravené k tomuto účelu.They are supplied fully assembled while the locks.Product with a load capacity of 75 kg and a top aluminum profile is highly resistant to damage and also has a long life.So if you are looking for high quality, you do not look further into.
Specifications can be changed without notice. Images are for illustrative purposes only. Retail Price (MSRP) means the manufacturer's/supplier's suggested retail price.
Link to Manufacturer's Website: Enter link to the external review


text:
Chapter 4.3
Rowena was still in her defeated mood when Vincent arrived again that evening. She didn't even turn around when she heard the door opening. Vincent was determined, he was going to make her pay attention and stop acting in this placid manner.
'Mother had a visitor today,' he said, trying to provoke a reaction. 'She didn't know I knew he was there, so she doesn't know I overheard their conversation.'
'I'm sure that in your little world, Vincent, this is a very interesting conversation, but unfortunately, it bores me to death, so either get to the point or get out.' Vincent flinched at the harshness of her words, but made him tell himself that it wasn't Rowena speaking, it was her hurt, anger and grief.
'He came from Fersere.'
'There are lots of people who live in Fersere; it's perfectly plausible that one of them can end up in your mother's house.'
'But I don't think this was just anyone. He knew you were locked up here.'
'There are also plenty of people who know that their Crown Princess is locked up in a tower somewhere.'
Vincent hesitated, knowing that if he said what he was going to say next, there was no going back. 'He was talking about Damia. How she's got as much to lose as my mother if she were to say that you were still alive.'
'This is where Damia has disappeared to.' Rowena leaned in, suddenly interested in what he was saying. 'What is she doing?'
'She's gone back with your Prince. I don't know exactly what she's meant to be doing once she gets back to Fersere but I think it's something to do with the strange man in my house, my mother, the throne and you.' He took a deep breath and prepared himself to disclose the biggest shock. 'But that's not everything. The man was talking about something else, something he was more worried about than Damia.'
'What's that?'
'He's worried you'll escape and make your way back to the royal palace in Geneta. He was talking about killing you.' He knew he'd got Rowena's attention because she was on her feet, her back pressed against the wall, her eyes wide with fear.
'Is that why you're here? I'm of no use to you any more so you're going to get rid of me?'
'No,' Vincent insisted, walking towards the Princess. 'I want to help you. This is my chance to escape too.' Rowena flinched as he approached, now deeply suspicious and untrusting of him, unsure what side he was on. 'I've been living with my mother forcing me to follow her into the family business all my life.'
'The family business being, kidnapping, murder, fraud…'
'Yes, that pretty much sums it up. But since you arrived, I've started to realize that I don't have to do exactly what my mother says. I don't want to be the bad guy, I want to do something worthwhile with my life, something honest.'
Rowena didn't quite know how to react to the secret Vincent had just told her. 'But I don't understand how you can help me. There is no way for me to get out of here without the key to the door at the bottom of the tower and how would me escaping help you anyway. It's not like I can send an army to take you away from your mother.'
'But I've come up with a plan,' Vincent said, excitedly, pleased with himself that he's managed to plant the seed of thought on the Princess's mind.
'Tell me more,' Rowena said, taking a step away from the wall, closer to Vincent.
'OK then, first we…'


text:
Prom Sale
- Complete Theme Kits
- New for 2017
- Photo Booth
- Banners
- Hanging Decorations
- Lighting
- Curtains
- Balloons
- Chandeliers
- Shop all...
Availability : Please select an option
Product Code : BEAPAW-PUR-HDR
The Purple Paw Necklaces feature a 2" paw medallion in your school color! This Purple Paw Bead measures 18" long. Sell them in your school store to raise money for your club or team! Sold individually or by the dozen.
Please allow ample time for delivery. Our estimated delivery date for this product is noted in checkout. Many items are shipped separately, so please allow extra time for these items to arrive. Shipping charges are based on the value of the merchandise and not the number of shipments. For additional shipping information, please contact our Customer Service Department at (1-800-314-8736).
Personalized Party Supplies for every celebration!
Personalized Sports Supplies for Fans and Game Day Experience Pros!


text:
Click here to view sale items.
Can't find the item you are looking for using the search and browse tools above? View our special order catalog.
Carry your flowers, groceries, books, whatever, with ease and style thanks to Sunlite's Wooden Basket. Made from real Mastaquoia wood, the Wooden basket adds class to any bike. It mounts to any handlebar easily and comes in 2 finishes.
Not yet rated. Be the first to
Write a Review


text:
In my previous article, we've done setting up the backbone of our Angular 2 application. And as promised, we will continue exploring Angular 2 within ASP.NET Core by using Web API to work with server-side data.
If you stumble into this article, then I will presume that you are new to Angular 2 and would like to get your hands dirty with practical examples. Just like you, I am also pretty much new to Angular 2 and that's why I am writing this series of articles as a reference to other ASP.NET developers who might want to jump on Angular 2.
Luckily, I had the opportunity to review a book on ASP.NET Web API and Angular 2 by Valerio De Sanctis, so it's a good timing to incorporate the things I've learned from that book into this series of articles. If you are looking for in-depth information about Angular 2 with ASP.NET Web API, then I would highly recommend you to grab yourself a copy of the book here .
In this series of articles, we will learn how to build a data-driven Angular 2 app from scratch.
What You Will Learn
In this particular series, you will learn the following:
- Overview of Angular 2 RC and Core Web API 2
- Upgrading from Angular 2 Beta to RC4
- Enable Serving Static Files and Diagnostics
- Adding the typings.json File
- Updating the package.json File
- Updating the tsconfig.json File
- Modifying the Existing Angular 2 Components
- Switching to Gulp
- Adding the systemjs.config.js File
- Updating the index.html
- Testing the App
- Overview of Web Request and Response Flow
- The Backpacker's Lounge App
- Restructuring the App
- Integrating NewtonSoft.JSON
- Creating the ViewModels
- Creating the Web API Controllers
- Creating the Client-Side ViewModels
- Creating the Client-Side Service
- Creating the Angular 2 Components
- Enabling Client-Side Routing
- Rewriting
- Running the App
- Tips
Before you go any further, though it's not required, I would suggest you to read my previous article about Getting Started with Angular 2 in ASP.NET Core since I will not be covering the details about setting up Angular 2 in ASP.NET Core in this series.
OverviewBefore we move further, let's talk about a brief recap about each framework:
Core Web API
ASP.NET Core Web API is a framework built on top of the .NET Core. It's made specifically for building RESTful services that can serve a massive range of clients including web browsers, mobile devices and more. ASP.NET Core has built-in support for MVC building Web APIs. Unifying the two frameworks makes it simpler to build apps.
Angular 2Angular 2 is the second major installment of AngularJS and is entirely written in TypeScript . For developers who are working with Angular 1.x, you might find a big change in Angular 2 as it is entirely component based and object orientation is much easier with enhanced DI capability.
Upgrading to Angular2 RC 4In my previous article, we were using Angular 2 Beta version. Recently, Angular 2 RC version was released, and to align with the current version, I think it's a good idea to upgrade.
Before making any file changes, make sure that you have the supported versions of Node and NPM. If you have older versions uninstall node and then download and install the required version here: Node.jsNPM is already integrated in the Node installer, so you don't have to worry about manually upgrading it.
After the installation, make sure to restart your machine to ensure updates will take effect. In my case I have the following versions installed:
- Node v6.3.1 (the current release as of this writing)
- NPM 3.10.3 (latest version as of this writing)
Let's Start Modifying!Once you're all set, we can now start modifying our existing Angular 2 App. If you haven't referred my previous article and would like to jump on this series, then make sure to create the files needed if they do not exist in your project.
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.0.0"Save the file to install the needed packages. Once installed, modify your Configure() method within Startup.cs so it will look like this:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseDefaultFiles(); app.UseStaticFiles(); if (env.IsDevelopment()) app.UseDeveloperExceptionPage(); app.UseMvc(); }There's nothing much to say about the code above. We just added a call to UseDefaultStaticFIiles() to enable the wwwroot to server static files, and call the UseDeveloperExceptionPage() to allow us see the error message in the page when something wrong is going to happen in our code.
Adding the typings.json FileBased on the official Angular 2 Quickstart demo, we need to create a new file for creating typings containing node, jasmine and core-js typings. To do that, just right-click on your project root and then add a new .json file. In my case, I have selected the "NPM Config File" and just rename it as "typings.json". Replace the default generated content with the following below:
{ "globalDependencies": { "core-js": "registry:dt/core-js#0.0.0+20160602141332", "jasmine": "registry:dt/jasmine#2.2.0+20160621224255", "node": "registry:dt/node#6.0.0+20160621231320" } }
Note: the core-js line in the typings.json file is the only required one, yet we've also taken the chance to add the jasmine and node typings: you could need them in the near future, should you want to use the Jasmine test framework and/or use code that references objects in the nodejs environment. Keep them there for now, they won't hurt your project.
Updating the package.json FileOpen the package.json file and replace everything in it with the following:
{ "version": "1.0.0", "name": "AngularJS2Demo", "author": "ProudMonkey", "description": "A simple DEMO on Angular 2 RC4 within ASP.NET Core 1.0 RTM", "scripts": { "postinstall": "typings install", "typings": "typings" }, "dependencies": { "@angular/common": "2.0.0-rc.4", "@angular/compiler": "2.0.0-rc.4", "@angular/core": "2.0.0-rc.4", "@angular/forms": "0.2.0", "@angular/http": "2.0.0-rc.4", "@angular/platform-browser": "2.0.0-rc.4", "@angular/platform-browser-dynamic": "2.0.0-rc.4", "@angular/router": "3.0.0-beta.1", "@angular/router-deprecated": "2.0.0-rc.2", "@angular/upgrade": "2.0.0-rc.4", "systemjs": "0.19.27", "core-js": "^2.4.0", "reflect-metadata": "^0.1.3", "rxjs": "5.0.0-beta.6", "tsd": "^0.6.5", "zone.js": "^0.6.12" }, "devDependencies": { "typescript": "^1.8.10", "gulp": "^3.9.1", "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", "gulp-sourcemaps": "^1.6.0", "gulp-typescript": "^2.13.6", "gulp-uglify": "^1.5.3", "typings": "^1.3.1", "gulp-tsc": "^1.2.0" } }If you may have noticed, we've added the scripts section to install the typings. Typings manages and install the TypeScript definitions. The packages with the @ symbol is part of the new Angular 2 bundle: the other ones are loading libraries, helper tools and polyfills for older browser support. The current version of Angular 2 as of this writing is 2.0.0-rc.4. You can check for the updates here. You may also have noticed that we've replaced the dependencies to Grunt with Gulp ~ I'll explain why later in this article. You may remove the existing gruntfile.js file just like what I did if you'd like, but if you prefer using Grunt, feel free do so. No one is going to fire you of using Grunt ïŠ Now, save the file to restore the needed packages for our application. Once installed, you should be able to see something like this: Figure 1: Application Dependencies
Tip: If Typings didn't install successfully on Save, then try to use the "Restore Package" option by right-clicking on the Dependencies node. Another way is to use the command line to run typings explicitly. To do that, just navigate to the root folder of your app and do a CTRL+SHIFT, then select "Open command window here". In the command line, type in the following command:
npm run typings installIf successful, you should be able to see something like this: Figure 2: Command Line Okay, switch back to Visual Studio and you should be able to see the typings folder within the root of your app just like in the figure below: Figure 3: Solution Explorer As a recap, all Angular 2 dependencies will be installed in the following location in your local drive:
Updating the tsconfig.json FileNow open your tsconfig.json file, if you do not have it, you need to create one. Here's the updated TypeScript JSON Configuration file:
{ "compileOnSave": true, "compilerOptions": { "target": "es5", "module": "system", "moduleResolution": "node", "experimentalDecorators": true, "emitDecoratorMetadata": true, "noImplicitAny": false, "suppressImplicitAnyIndexErrors": true, "noEmitOnError": true, "removeComments": false, "sourceMap": true }, "exclude": ["node_modules", "wwwroot", "Scripts/app"] }The compileOnSave signals the IDE to generate all files for a given tsconfig.json upon saving. compilerOptions configuration will influence how the Intellisense, and our external TypeScript compiler will work. By excluding the "Scripts/app" folder in the config, we tell the built-in TypeScript compiler provided by Visual Studio 2015 to disable compiling the external TypeScript files within that location.
Changes on Angular 2 ComponentsSince we are upgrading to RC version, we also need to update the components that have references to previous beta versions of Angular. We are going to split each component update for you to easily follow.
The app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'angularjs2demo', template: `<h1>AngularJS 2 Demo</h1><div>Hello ASP.NET Core! Greetings from AngularJS 2.</div>` }) export class AppComponent {} There' s not much to say about the update above.We just changed the import reference to the new name which is @angular / core. The boot.ts ///<reference path="../../typings/globals/core-js/index.d.ts"/> import { bootstrap } from '@angular/platform-browser-dynamic'; import { AppComponent } from './app.component'; import 'rxjs/Rx'; bootstrap(AppComponent);Just like in the app.component.ts file, we changed the reference to the new Angular bundle. Notice that we also change the typings reference.
Switching to GulpThis time, we will be using Gulp as the JavaScript Task Runner to automate our client-side scripts. I'd like to note that switching to Gulp does not mean Grunt is a bad tool. Of course Grunt is still a great tool and used by many. If you prefer, you can always use it to automate tasks for you. I just preferred to use Gulp because of its conciseness and easy task writing. It also uses node.js' streams, and executes faster, since it does not open/close files, or create intermediary copies all the time. But then again, every developer has a preference, so it's all up to you which JS task runners you would use ïŠ.
var gulp = require('gulp'), gp_concat = require('gulp-concat'), gp_sourcemaps = require('gulp-sourcemaps'), gp_typescript = require('gulp-typescript'), gp_uglify = require('gulp-uglify'), gp_clean = require('gulp-clean'); // Define the source paths var srcPaths = { app: ['Scripts/app/boot.ts', 'Scripts/app/**/*.ts'], js: ['Scripts/js/**/*.js', 'node_modules/core-js/client/shim.min.js', 'node_modules/zone.js/dist/zone.js', 'node_modules/reflect-metadata/Reflect.js', 'node_modules/systemjs/dist/system.src.js', 'node_modules/typescript/lib/typescript.js', 'node_modules/ng2-bootstrap/bundles/ng2-bootstrap.min.js', 'node_modules/moment/moment.js'], js_angular: ['node_modules/@angular/**'], js_rxjs: ['node_modules/rxjs/**'] }; var destPaths = { app: 'wwwroot/app/', js: 'wwwroot/js/', js_angular: 'wwwroot/js/@angular/', js_rxjs: 'wwwroot/js/rxjs/' }; gulp.task('clean', function() { return gulp.src(destPaths).pipe(clean()); }); // Compile, minify and create sourcemaps for all TypeScript files and // place them to wwwroot/app, together with their js.map files. gulp.task('app', function() { return gulp.src(srcPaths.app).pipe(gp_sourcemaps.init()).pipe(gp_typescript(require('./tsconfig.json').compilerOptions)).pipe(gp_uglify()).pipe(gp_sourcemaps.write('/')).pipe(gulp.dest(destPaths.app)); }); // Copy all JS files from external libraries to wwwroot/js gulp.task('js', function() { gulp.src(srcPaths.js_angular).pipe(gulp.dest(destPaths.js_angular)); gulp.src(srcPaths.js_rxjs).pipe(gulp.dest(destPaths.js_rxjs)); return gulp.src(srcPaths.js).pipe(gulp.dest(destPaths.js)); }); // Watch specified files and define what to do upon file changes gulp.task('watch', function() { gulp.watch([srcPaths.app, srcPaths.js], ['app', 'js']); }); // Define the default task so it will launch all other tasks gulp.task('default', ['app', 'js', 'watch']);The code above contains three (3) main variables:
- gulp - initializes each Gulp plugins that we are going to need for running the tasks.
- srcPaths - holds an array of sources that we want to copy and transpile.
- destPaths - holds an array of specific location within wwwroot. This is basically where we dump the scripts we defined from the srcPaths.
It also contains five (5) main tasks:
- clean - This task deletes the existing files from the destination folders we defined.
- app - This task compiles, uglify and create sourcemaps for all TypeScript files and place them to wwwroot/app folder, together with their js.map files.
- js - This task will copy all JavaScript files from external libraries which is located within the node_modules folder and place them to wwwroot/js folder.
- watch - This task watches files defined in app and js tasks that are changed.
- default - Define the default task so it will launch all other tasks
Adding the systemjs.config.js FileNow, let's add the systemjs configuration file. Right-click on the wwwroot folder and then select Add > New Item. Under Client-side templates, select "JavaScript File" just like in the figure shown below:
Figure 5: Add New Item Dialog
Name the file as "systemjs.config.js" and then copy the following code below:
(function(global) { // map tells the System loader where to look for things var map = { 'app': 'app', // our application files '@angular': 'js/@angular', // angular2 packages 'rxjs': 'js/rxjs' // Rxjs package }; // packages tells the System loader which filename and/or extensions //to look for by default (when none are specified) var packages = { 'app': { main: 'boot.js', defaultExtension: 'js' }, 'rxjs': { defaultExtension: 'js' } }; // configure @angular packages var ngPackageNames = ['common', 'compiler', 'core', 'forms', 'http', 'platform-browser', 'platform-browser-dynamic', 'router', 'router-deprecated', 'upgrade', ]; function packIndex(pkgName) { packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' }; } function packUmd(pkgName) { packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' }; }; var setPackageConfig = System.packageWithIndex ? packIndex : packUmd; ngPackageNames.forEach(setPackageConfig); var config = { map: map, packages: packages } System.config(config); })(this);
The SystemJS.config file will load the application and library modules. We are going to need this loader to build our Angular 2 app. For more details about SystemJS config, read on: SystemJS
Updating the index.htmlNow, since we need the SystemJS config to load our app modules and components, then we need to update our index.html to include the new configuration. Replace your index.html file so it will look something like this:
<html> <head> <base href="/"> <title>ASP.NET Core with Angular 2 RC Demo</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Step 1. Load libraries --> <!-- Polyfill(s) for older browsers --> <script src="js/shim.min.js"></script> <script src="js/zone.js"></script> <script src="js/Reflect.js"></script> <script src="js/system.src.js"></script> <!-- Step 2. Configure SystemJS --> <script src="systemjs.config.js"></script> <script> System.import('app').catch(function(err) { console.error(err); }); </script> </head> <!-- Step 3. Display the application --> <body> <!-- Application PlaceHolder --> <angularjs2demo>Please wait...</angularjs2demo> </body> </html>
Testing the AppClean and Build your solution and make sure it has no errors. If it builds successfully, then right click on the gulpfile.js and select "Task Runner Explorer"
Make sure to click the "Refresh" button to load the task as shown in the figure below:
Figure 6: Task Runner Explorer
Now, right-click to the default task and hit Run. You should be able to see something like this when successful.
Figure 7: Task Runner Results
After successfully running the Task Runner, you should also see that the "app" and "js" folders are generated within your "wwwroot" folder as shown in the figure below:
Figure 8: The wwwroot
Running the application should result in something like this:
Figure 9: The Output
If you've made it this far, then congrats! You have now an Angular 2 RC4 running, and now ready to work with data using Core Web API.Tips:
- If you are getting the following Typescript Build errors:
"TS2664 Invalid module name in augmentation, module '../../Observable' cannot be found."
Just follow the solution provided here: https://github.com/Microsoft/TypeScript/issues/8518#issuecomment-229506507
In my case, since I'm using VStudio 2015 Update 3, the fix was to replace the typescriptService.js with the updated version mentioned from the link above.
- If you are getting errors during compilation that says something like these:
TS1005 Build:'=' expected.
TS1005 Build:';' expected.
Just make sure that you exclude the wwwroot in your ts.config. If that doesn't work then you can try the following options for the fix:
Option 1: Filtering out the location that contains the *.d.ts files from compiling in your ts.config file.
Option 2: Upgrade to TypeScript 1.9.x or greater Beta versions by running the following command within your project root:
npm install typescript@next
Creating the ApplicationBefore we get our hands dirty, let's have a quick overview on what happens when a Request is issued by the client and how the server sends out a JSON Response by looking at the figure below:
Figure 10: Web Request and Response Flow
Async Data Requests are any user interactions, as long as it needs to retrieve data from the server, a very typical example includes (yet is not limited to): clicking a button to show data or to edit/delete something, navigating to another view, submitting a form and so on. And in order to respond to any client-issued Async Data Request, we need to build a server-side Web API Controller that would handle the client request (CRUD). In other words, the API will process the request, perform database CRUD operations and return a serialized ViewModel to the client as a Response.
To understand how Angular and Web API connects to each other in real scenarios then let's go ahead and start building our app.
The Backpacker's Lounge AppFor the entire series, we will be creating an application that covers a bunch of CRUD operations. I love to travel and explore the wonders of nature, so to make this app different and interesting for starters, I have decided to build a simple Backpackers Lounge App.
To make it more clear, we will be creating something like this:
Figure 11: The Main App Layout
In this part of the series, we are going to focus on creating the Home content. Meaning, we are going to create the components for displaying the latest discussion, latest places added and the most viewed places. We are also going to setup the client-side routing and see how the navigation works in Angular 2. Adding to that, we will implement a simple master-detail navigation to see how we are going to pass information between components. Finally, we are going to see the basics on how the two-way data-binding will work in Angular2.
Looking at the figure above, we are going to need the following sets of API calls:
- api/Lounge/GetLatestDiscussion
- api/Place/GetLatestEntries
- api/Place/GetMostViewed
Let's Get Started!
Restructuring the AppLooking at the picture shown in figure 11, we need to add some folders to our existing "app" folder and then move some existing files. Let's start by adding the following folders below under Scripts/app folder:
- components
- services
- viewmodels
- home
- lounge
- about
- account
- explore
import {AppComponent} from './components/app.component';
Creating the ViewModelsTo provide you a quick recap, ViewModels are just classes that house some properties that we only need in the View/UI. From this moment, we are going to use ViewModels as our data transfer objects: sending data from client to server and/or vice-versa.
- LoungeViewModel.cs
- PlaceViewModel.cs
The LoungeViewModelUpdate the default code generated so it would look similar to the following code below:
using System; using Newtonsoft.Json; namespace AngularJS2Demo.ViewModels { [JsonObject(MemberSerialization.OptOut)] public class LoungeViewModel { public int ID { get; set; } public string Subject { get; set; } public string Message { get; set; } [JsonIgnore] public int ViewCount { get; set; } public DateTime CreatedDate { get; set; } public DateTime LastModifiedDate { get; set; } } }
The PlaceViewModelAnd here's the code for the PlaceViewModel class:
using System; using Newtonsoft.Json; namespace AngularJS2Demo.ViewModels { [JsonObject(MemberSerialization.OptOut)] public class PlaceViewModel { public int ID { get; set; } public string Name { get; set; } public string Location { get; set; } [JsonIgnore] public int ViewCount { get; set; } public DateTime CreatedDate { get; set; } public DateTime LastModifiedDate { get; set; } } }Both ViewModels above is nothing but just a class that houses some properties. Each class is decorated with the [JsonObject(MemberSerialization.OptOut)] attribute that causes the properties to be serialized into JSON unless being decorated by an explicit [JsonIgnore]. We're making this choice because we're going to need most of our ViewModel's properties serialized, as we'll be seeing soon enough. We are also limiting the properties defined in our ViewModels at this point. Eventually, we will be adding more to them once we integrate Authorizan, Authentication and Image for places. So for now let's keep rolling. Creating the Web API Controllers Now, let's create the needed API Controllers in our app.
The LoungeControllerCreate a new class under "Controllers" folder and name it as "LoungeController". To do that, just right click on the "Controllers" folder and then select Add > New Item. Under ASP.NET template, select "Web API Controller Class". Replace the default generated code with the following:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using AngularJS2Demo.Data.ViewModels; using Newtonsoft.Json; namespace AngularJS2Demo.Controllers { [Route("api/[controller]")] public class LoungeController: Controller { [HttpGet("{id}")] public IActionResult Get(int id) { return new JsonResult(GetTestData().Where(i => i.ID == id), DefaultJsonSettings); } [HttpGet("GetLatestDiscussion")] public IActionResult GetLatestDiscussion() { return GetLatestDiscussion(DefaultNumberOfItems); } [HttpGet("GetLatestDiscussion/{n}")] public IActionResult GetLatestDiscussion(int n) { var data = GetTestData().OrderByDescending(i => i.CreatedDate).Take(n); return new JsonResult(data, DefaultJsonSettings); } private List < LoungeViewModel > GetTestData(int num = 99) { List < LoungeViewModel > list = new List < LoungeViewModel > (); DateTime date = DateTime.Now.AddDays(-num); for (int id = 1; id <= num; id++) { list.Add(new LoungeViewModel() { ID = id, Subject = String.Format("Discussion {0} Subject", id), Message = String.Format("This is a sample message for Discussion {0} Subject: It's more fun in the Philippines", id), CreatedDate = date.AddDays(id), LastModifiedDate = date.AddDays(id), ViewCount = num - id }); } return list; } private JsonSerializerSettings DefaultJsonSettings { get { return new JsonSerializerSettings() { Formatting = Formatting.Indented }; } } private int DefaultNumberOfItems { get { return 10; } } private int MaxNumberOfItems { get { return 50; } } } }
The class above make use of Attribute Routing to determine that the class is an API by decorating with this attribute: [Route("api/[controller]")].The class above contains the following three (3) main API action methods:
- Get() – This method returns a single set of data based on a given ID. This method can be invoked using: /api/lounge/<id> where id is a variable
- GetLatestDiscussion() – This method represents a RESTFUL API method, and just calls it's overload method to actually process the request. This method can be invoked using: /api/lounge/getlatestdiscussion
- GetLatestDiscussion(int n) – This method is the overload of GetLatestDiscussion() which takes an int parameter. It use the LINQ OrderByDescending() and Take operators to get the latest records from our test data. This method can be invoked using: /api/lounge/getlatestdiscussion/<n> where n is a variable, representing a number.
It also contains the following private methods:
- GetTestData(int num = 99) - A test method that returns a static list of items. We will be using this method to generate a list of LoungViewModel just for us to test something out quickly.
- DefaultJsonSettings - A property that gets the default JSON format settings
- DefaultNumberOfItems - A property that returns the defaul number of items to be returned in the UI.
- MaxNumberOfItems - A property that returns the default maximum number of items to be returned to the UI.
We are going to use the aforementioned private members for displaying a test data in our app. We will split and decouple our data access from within our Controller in next part of this series, but for now, let's just keep rolling.
The PlaceControllerCreate another Web API Controller Class and name it as "PlaceController". Again, replace the default code with the following:
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using AngularJS2Demo.Data.ViewModels; using Newtonsoft.Json; namespace AngularJS2Demo.Controllers { [Route("api/[controller]")] public class PlaceController: Controller { [HttpGet("{id}")] public IActionResult Get(int id) { return new JsonResult(GetTestData().Where(i => i.ID == id), DefaultJsonSettings); } [HttpGet("GetLatestEntries")] public IActionResult GetLatestEntries() { return GetLatestEntries(DefaultNumberOfItems); } [HttpGet("GetLatestEntries/{n}")] public IActionResult GetLatestEntries(int n) { var data = GetTestData().OrderByDescending(i => i.CreatedDate).Take(n); return new JsonResult(data, DefaultJsonSettings); } [HttpGet("GetMostViewed")] public IActionResult GetMostViewed() { return GetMostViewed(DefaultNumberOfItems); } [HttpGet("GetMostViewed/{n}")] public IActionResult GetMostViewed(int n) { if (n > MaxNumberOfItems) n = MaxNumberOfItems; var data = GetTestData().OrderByDescending(i => i.ViewCount).Take(n); return new JsonResult(data, DefaultJsonSettings); } private List < PlaceViewModel > GetTestData(int num = 999) { List < PlaceViewModel > list = new List < PlaceViewModel > (); DateTime date = DateTime.Now.AddDays(-num); for (int id = 1; id <= num; id++) { list.Add(new PlaceViewModel() { ID = id, Name = String.Format("Place {0} Name", id), Location = String.Format("Place {0} Location", id), CreatedDate = date.AddDays(id), LastModifiedDate = date.AddDays(id), ViewCount = num - id }); } return list; } private JsonSerializerSettings DefaultJsonSettings { get { return new JsonSerializerSettings() { Formatting = Formatting.Indented }; } } private int DefaultNumberOfItems { get { return 5; } } private int MaxNumberOfItems { get { return 100; } } } }
You may have noticed that both classes have the pretty much similar implementation. We could probably merge it in one Controller, but I have decided to split it with different class to value the separation of concerns.
You may also have noticed that both classes have the same private members. We could minimize that by creating a separate class, and implement the code there. But for now, let's just have it on separate class for easy reference.
Creating the Client-Side ViewModelsWe will be using TypeScript to define a set of class for us to work with type definitions. In other words, we will not be dealing with raw JSON data and anonymous objects; instead we will be using typed objects: an actual instance of classes.
Under Scripts/app/viewmodels, add a new TypeScript file and name it as "lounge.ts". Then copy the following code below:
export class Lounge { constructor( public ID: number, public Subject: string, public Message: string ) { } }
Add another TypeScript file and name it as "place.ts". Copy the following code below:
export class Place { constructor( public ID: number, public Name: string, public Location: string ) { } }
Notice that we're not adding all the properties that are present in our server-side ViewModel class: as a general rule of the thumb, we'll be keeping these classes as lightweight as possible, defining only what we need in the UI: we can always add more properties later, as soon as we need them.
These ViewModels will be used as a client-side, TypeScript class to properly map our JSON-serialized server-side ViewModels that is returned from the Web API controller.
Note: The property names should match with the property names you defined in your server-side ViewModels including the casing.
Creating the Client-Side Service
Now, we need to setup a client-service to fetch the required data from the Web API: we'll do that by issuing a request to the API Controllers we built earlier. We are going to use the Angular Http client to communicate via XMLHttpRequest (XHR), which is a rather complex HTTP-based API that provides client functionality for transferring data between a client and a server.
Create another TypeScript file under "Scripts/app/services" folder and name it as "app.service.ts". Copy the following code below:
The AppService Class
import { Injectable } from "@angular/core"; import { Http, Response } from "@angular/http"; import { Lounge } from "../viewmodels/lounge"; import { Observable } from "rxjs/Observable"; @Injectable() export class AppService { constructor(private http: Http) {} private loungeBaseUrl = 'api/lounge/'; // URL to web api private placeBaseUrl = 'api/place/'; // URL to web api getLatestDiscussion(num ? : number) { var url = this.loungeBaseUrl + "GetLatestDiscussion/"; if (num != null) url += num; return this.http.get(url).map(response => response.json()).catch(this.handleError); } getDiscussion(id: number) { if (id == null) throw new Error("id is required."); var url = this.loungeBaseUrl + id; return this.http.get(url).map(response => < Lounge > response.json()).catch(this.handleError); } getLatestEntries(num ? : number) { var url = this.placeBaseUrl + "GetLatestEntries/"; if (num != null) url += num; return this.http.get(url).map(response => response.json()).catch(this.handleError); } getMostViewed(num ? : number) { var url = this.placeBaseUrl + "GetMostViewed/"; if (num != null) url += num; return this.http.get(url).map(response => response.json()).catch(this.handleError); } getPlace(id: number) { if (id == null) throw new Error("id is required."); var url = this.placeBaseUrl + id; return this.http.get(url).map(response => < Lounge > response.json()).catch(this.handleError); } private handleError(error: Response) { console.error(error); return Observable.throw(error.json().error || "Server error"); } }
Notice that the code above pretty much resembles our Web API Controllers methods, except that we have merged all the calls to our API within it. This will be the class that our client will use to fetch the data from our WebAPI Controller itself.
Keep in mind that we make use of the Injectable decorator, declaring that the service is an Injectable class: doing that will attach to our class a set of meta data that will be consumed by the DI system upon instantiation. Basically what we're doing here is telling the DI injector that the constructor parameter(s) should be instantiated by using their declared type(s). The TypeScript code allows a very fluent syntax to achieve this result at constructor level, as it can be seen in the following line:
constructor(private http: Http) { }
Creating the Angular 2 Components
The next thing that we are going to do is to create a dedicated component for serving different contents. Let's start by implementing the "Latest Discussion" master-detail components.
The Lounge List Component
Create a new TypeScript file under "Scripts/app/components/lounge" and name the file as "lounge-list.component.ts". Copy the following code below within that file:
import { Component, OnInit } from "@angular/core"; import { HTTP_PROVIDERS } from "@angular/http"; import { Router } from "@angular/router"; import { Lounge } from "../../viewmodels/lounge"; import { AppService } from "../../services/app.service"; import { LoungeDetailComponent } from "./lounge-detail.component"; @Component({ selector: "lounge-list", template: ` <h2>{{title}}</h2> <ul class="items"> <li *ngFor="let item of items" [class.selected]="item === selectedItem" (click)="onSelect(item)"> <span>{{item.Subject}}</span> </li> </ul> `, styles: [` ul.items li { cursor: pointer; } ul.items li:hover { background-color: #E8FAEC; } `], directives: [LoungeDetailComponent], providers: [ HTTP_PROVIDERS, AppService ] }) export class LoungeListComponent implements OnInit { title: string; selectedItem: Lounge; items: Lounge[]; errorMessage: string; constructor(private AppService: AppService, private router: Router) {} ngOnInit() { this.title = "The Lounge"; var service = this.AppService.getLatestDiscussion(); service.subscribe(items => this.items = items, error => this.errorMessage = < any > error); } onSelect(item: Lounge) { this.selectedItem = item; var link = ['/lounge', this.selectedItem.ID]; this.router.navigate(link); } }
The LoungeListComponent will display the latest list of discussions from our mocked data, which is returned from the Web API call. Let's talk a bit about what we did there:
At the top of the file, we have imported the Angular classes that we need: since we're creating a Component, we need the Component base class by referencing the "@angular/core", and we also need to implement the OnInit interface because our component needs to execute something upon its initialization. As for the HTTP_PROVIDERS, these are a set of injectables that are required any time we need to use the Angular built-in Http service. We have referenced the Angular Router to make use of client-side navigation because we are going to need it for navigating to the details component. We have also referenced the service that we have created earlier to communicate with our server to get some data. Finally, we have imported the lounge-detail component for displaying the details content which we will be creating soon enough.
The @component block is where we setup the UI for our Component, including the selector, template and styles. Notice that we used a bit of Angular 2 Template Syntax there, in order to get the job done. Specifically, we used a master template, a ngFor directive, a property binding and an event binding. Note that we can also decouple the template and styles in a separate file by defining the templateUrl and styleUrls. It is also where we define the directives and providers that the Component needs.
The LoungeListComponent is a class written in TypeScript. This class contains some properties, a constructor which makes use of DI to instantiate the AppService and Router objects. It also composed of methods that will be used in the component itself, specifically in the Angular 2 template manipulation, and of course in the client-side routing. The ngOnInit() method is where we get the data from the service which fires on initialization of the component. The onSelect() method takes a Lounge object as the parameter. This is where we get the selected item and pass it to the details component by making use of Angular 2 routing. For information about Angular 2 Templates, read on: Template Syntax
The LoungeDetail Component
Create another TypeScript component within "Scripts/app/components/lounge" and name the file as "lounge-detail.component.ts". Now copy the following code below within that file:
import { Component, OnInit, OnDestroy } from "@angular/core"; import { HTTP_PROVIDERS } from "@angular/http"; import { Router, ActivatedRoute } from "@angular/router"; import { AppService } from "../../services/app.service"; import { Lounge } from "../../viewmodels/lounge"; @Component({ selector: "lounge-detail", template: ` <div *ngIf="item" class="item-details"> <h2>{{item.Subject}} - Detail View</h2> <ul> <li> <label>Subject:</label> <input [(ngModel)]="item.Subject" placeholder="Insert the title..."/> </li> <li> <label>Message:</label> <textarea [(ngModel)]="item.Message" placeholder="Insert a message..."></textarea> </li> </ul> </div> <div> <button (click)='onBack()'>Back to Home</button> </div> `, providers: [ HTTP_PROVIDERS, AppService ], styles: [` .item-details { margin: 5px; padding: 5px 10px; border: 1px solid 9BCCE0; background-color: #DDF0D5; width: 500px; } .item-details * { vertical-align: middle; } .item-details ul li { padding: 5px 0; } `] }) export class LoungeDetailComponent implements OnInit { item: Lounge; sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) {} ngOnInit() { this.sub = this.route.params.subscribe(params => { var id = +params['id']; console.log("selected id " + id); this.AppService.getDiscussion(id).subscribe(item => this.item = item[0]); }); } ngOnDestroy() { this.sub.unsubscribe(); } onBack(): void { this.router.navigate(['/home']); } }
Just like what we did in the lounge-list.component.ts file, we are importing what we need for this specific component. Within template, noticed that we've used the Angular 2 ngIf directive to hide the DOM elements when the property item is null or no data associated on it. We also used the Angular 2 ngModel directive to implement a two-way data binding syntax for textarea element. This could simply mean that any changes made in the binded element will automatically update the model itself and vice-versa.
We also applied some simple styles to our HTML by defining the @component's styles attribute just to beautify a bit our rendered markup.
The LoungeDetailComponent class implements the logic for this particular component. We have defined a few properties and make use of constructor injection (DI) to initialize objects what we need for the class implementation. This class takes the id parameter from the params observable in the ActivatedRoute service and use the AppService to fetch the data with that id. You should be able to see how we did that under the ngOnInit() method. The ngOnDestroy() method is responsible for cleaning up the params subscription. Finally, we have implemented an onBack() method to let user navigate back to the Home component view. This event is attached in the Button element defined in our HTML template using Angular 2 event binding.
At this point, we're done with our basic Lounge master-detail navigation and data binding. Now, let's do the same to implement the "What's New?" and "Top Places to Visit" master-detail components.
The PlaceList Component
Create a new TypeScript file under "Scripts/app/components/explore" and name the file as "place-list.component.ts". Copy the following code below within that file:
import { Component, Input, OnInit } from "@angular/core"; import { HTTP_PROVIDERS } from "@angular/http"; import { Router } from "@angular/router"; import { Place } from "../../viewmodels/place"; import { AppService } from "../../services/app.service"; import { PlaceDetailComponent } from "./place-detail.component"; @Component({ selector: "place-list", template: ` <h2>{{title}}</h2> <ul class="items"> <li *ngFor="let item of items" (click)="onSelect(item)"> <span>{{item.Name}}</span> </li> </ul> `, styles: [` ul.items li { cursor: pointer; } ul.items li:hover { background-color: #E8FAEC; } `], directives: [PlaceDetailComponent], providers: [ HTTP_PROVIDERS, AppService ] }) export class PlaceListComponent implements OnInit { @Input() class: string; title: string; items: Place[]; errorMessage: string; constructor(private AppService: AppService, private router: Router) {} ngOnInit() { var service = null; switch (this.class) { case "latest": default: this.title = "What's New?"; service = this.AppService.getLatestEntries(); break; case "most-viewed": this.title = "Top Places to Visit"; service = this.AppService.getMostViewed(); break; } service.subscribe(items => this.items = items, error => this.errorMessage = < any > error); } onSelect(item: Place) { var link = ['/explore', item.ID]; this.router.navigate(link); } }
The code above has similar implementation with our LoungeListComponent except that we did something different in the ngOnInit() method. We have implemented a switch statement to determine which data to load in the component by passing the class variable as the parameter. The class variable is defined using an @Input() decorator function (@Input() class: string;), that will add the required metadata to make this property available for property binding. We need to do that because we expect this property to be populated from a binding expression within a parent component ~ the Home Component. We did it like that to make this component more reusable and maintainable since the data for "What's New?" and "Top Places to Visit" components are coming from the same data source.
The PlaceDetail Component
The details for "What's New?" and "Top Places to Visit" components will also share the same Detail-View component. Let's go ahead and create it. Add a new TypeScript file on the same folder and name it as "place-detail.component.ts". Now copy the following code below:
import { Component, OnInit, OnDestroy } from "@angular/core"; import { HTTP_PROVIDERS } from "@angular/http"; import { Router, ActivatedRoute } from "@angular/router"; import { AppService } from "../../services/app.service"; import { Place } from "../../viewmodels/place"; @Component({ selector: "place-detail", template: ` <div *ngIf="item" class="item-details"> <h2>{{item.Name}} - Detail View</h2> <ul> <li> <label>Subject:</label> <input [(ngModel)]="item.Name" placeholder="Insert the name..."/> </li> <li> <label>Message:</label> <textarea [(ngModel)]="item.Location" placeholder="Insert a location..."></textarea> </li> </ul> </div> <div> <button (click)='onBack()'>Back to Home</button> </div> `, providers: [ HTTP_PROVIDERS, AppService ], styles: [` .item-details { margin: 5px; padding: 5px 10px; border: 1px solid 9BCCE0; background-color: #DDF0D5; width: 500px; } .item-details * { vertical-align: middle; } .item-details ul li { padding: 5px 0; } `] }) export class PlaceDetailComponent implements OnInit { item: Place; sub: any; constructor(private AppService: AppService, private router: Router, private route: ActivatedRoute) {} ngOnInit() { this.sub = this.route.params.subscribe(params => { var id = +params['id']; this.AppService.getPlace(id).subscribe(item => this.item = item[0]); }); } ngOnDestroy() { this.sub.unsubscribe(); } onBack(): void { this.router.navigate(['/home']); } }
The code above is pretty much the same as what we did for implementing the LoungeDetailComponent, so there's really not much to talk about.
The Home Component
Create a new TypeScript file under Scripts/app/components/home and name the file as "home.component.ts". Copy the following code below within that file:
import { Component } from "@angular/core"; import { LoungeListComponent } from "../lounge/lounge-list.component"; import { PlaceListComponent } from "../explore/place-list.component"; @Component({ selector: "home", template: ` <div class="div-wrapper"> <div class="div-lounge"> <div> <lounge-list class="discussion"></lounge-list> </div> </div> <div class="div-explore"> <div class="top"> <place-list class="latest"></place-list> </div> <div class="bot"> <place-list class="most-viewed"></place-list> </div> </div> <div class="div-clear"></div> </div> `, directives: [ LoungeListComponent, PlaceListComponent ], styles: [` .div-wrapper { margin-right: 300px; } .div-lounge { float: left; width: 100%; } .div-lounge div{ margin:0 0 10px 0; border: 1px solid #9BCCE0; background-color: #DDF0D5; } .div-explore { float: right; width: 300px; margin-right: -300px; } .div-explore .top{ margin:0 10px 10px 10px; border: 1px solid #9BCCE0; background-color: #DDF0D5; } .div-explore .bot{ margin:10px 10px 10px 10px; border: 1px solid #9BCCE0; background-color: #DDF0D5; } .div-clear { clear: both; } `] }) export class HomeComponent {}
The HomeComponent will serve as our master page for displaying the list of data from various components. Notice that we've imported the LoungeListComponent and PlaceListComponent files so we can integrate those components within our Home component. We added the <lounge-list> element to display the "Latest Discussion". Also, we've added the <place-list> element to display the "What's New?" and "Top Places to Visit" list with a standard class attribute to uniquely identify each one of them. The class attribute defined in the element will be used as the target of a property binding: We were referring to the @Input() decorator attribute which we defined in the PlaceListComponent.
We also use the class attribute to uniquely define a sets of styles for each elements: As we can see from the Angular 2 template's styles section above.
Enabling Client-Side Routing
At this point, we've done implementing the needed components for our Angular 2 application yet it has a few major issues. The navigation we've setup in our master-detail components will not work since the URL routes defined from our components doesn't exist yet. Also, we need to configure our app to enable client-side routing. It's time for us to connect the dots in the picture to achieve what we expect.
Creating the App.Routes
Create a new TypeScript file under "/Scripts/app" folder and name it as "app.routes.ts". Copy the following code below:
import { provideRouter, RouterConfig } from "@angular/router"; import { HomeComponent } from "./components/home/home.component"; import { LoungeDetailComponent } from "./components/lounge/lounge-detail.component"; import { PlaceDetailComponent } from "./components/explore/place-detail.component"; const routes: RouterConfig = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'lounge/:id', component: LoungeDetailComponent }, { path: 'explore/:id', component: PlaceDetailComponent } ]; export const appRouterProviders = [ provideRouter(routes) ];
Let's see what we just did there.
Just like the other regular components, we need to import the module/directives that the component needs. It this case, we've imported the "@angular/router" to make use of the Angular 2 router interface: Specifically, we need to use and declare routes by defining a RouterConfig and define an export to add the router to our bootstrap.
The RouterConfig is an array of route definitions. A route is typically composed of two main parts: The path and component. From the code above, we have defined some routes that we need for this particular series. The first route indicates our default component that is targeting to our Home component. The rest of the routes are pointing to our components we created earlier.
Making the Routes Available
Let's modify the boot.ts file to make the routes available to any components that needs it.
///<reference path="../../typings/globals/core-js/index.d.ts"/> import {AppComponent} from "./components/app.component"; import {bootstrap} from "@angular/platform-browser-dynamic"; import { appRouterProviders } from "./app.routes"; import 'rxjs/Rx'; bootstrap(AppComponent, [appRouterProviders]);
What we did there is we imported the appRouterProviders that we have defined in our "app.routes.ts" which contains our configured router and make it available to the application by adding it to the bootstrap array.
Modifying the App Component
Now, we need to add an anchor tag to the template which, when clicked, triggers navigation to the Components. To do that, open "app.component.ts" file and update the code so it would look something like this:
import {Component} from '@angular/core'; import {ROUTER_DIRECTIVES} from '@angular/router'; import {HomeComponent} from './home/home.component'; @Component({ selector: "angularjs2demo", template: ` <h1>{{title}}</h1> <h3>{{subTitle}}</h3> <div class="menu"> <a class="home" [routerLink]="['/home']">Home</a> | </div> <router-outlet></router-outlet> `, directives: [ ROUTER_DIRECTIVES, HomeComponent ] }) export class AppComponent { title = "The Backpackers' Lounge"; subTitle = "For geeks who want to explore nature beyond limits."; }
At this point, we only need to import the Home component. The [routerLink] binding in the anchor tag tells the router where to navigate when a user clicks a specific link. The <router-outlet> tag acts as the placeholder element for the component that will be injected into the page DOM by the routing engine whenever the associated route is visited. Notice that we placed it right after our new navigation menu, so it will keep its position as the page content changes.
Adding the base Tag
Open "wwwroot/index.html" and add <base href="/"> at the very top within the <head> section. Our updated index.html file should now look something like this:
<html> <head> <base href="/"> <title>ASP.NET Core RTM with Angular 2 RC4 Demo</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Step 1. Load libraries --> <!-- Polyfill(s) for older browsers --> <script src="js/shim.min.js"></script> <script src="js/zone.js"></script> <script src="js/Reflect.js"></script> <script src="js/system.src.js"></script> <!-- Step 2. Configure SystemJS --> <script src="systemjs.config.js"></script> <script> System.import('app').catch(function(err) { console.error(err); }); </script> </head> <!-- Step 3. Display the application --> <body> <!-- Application PlaceHolder --> <angularjs2demo>Please wait...</angularjs2demo> </body> </html>
We need to set the base tag as it will tell the routing engine how to compose all of the upcoming navigation URLs our app will eventually have. For details, see: Router Base HREF
The last and final step is to handle rewrites in our web.config file. We need to tell the Web Server to rewrite all routing URLs, including the root one, to the index.html file by adding the following lines to the <system.webServer> section of web.config:
<rewrite> <rules> <rule name="Angular2 pushState routing" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> <add input="{REQUEST_FILENAME}" pattern=".*\.[\d\w]+$" negate="true" /> <add input="{REQUEST_URI}" pattern="^/(api)" negate="true" /> </conditions> <action type="Rewrite" url="/index.html" /> </rule> </rules> </rewrite>
By implementing the rules above, we're basically asking our Web Server to re-address any incoming request to the /index.html file, with the sole exception of those pointing to:
- Any existing file (to preserve references to actual .js, .css, .pdf, image files & more).
- Any existing folder (to preserve references to actual, potentially-browsable and/or Angular-unrelated subfolders).
- Anything within the /api/ folder (to preserve any call to our Web API Controllers).
Running the App
Now try to compile and rebuild your project and make sure that your gulp task is running. Hit F5 and you should be able to see something like this as the output:
Notice that the URL changes after clicking an item from the list and routes it to the corresponding details view. You can also see that the data changes automatically as you type: That's how the two-way data binding works in Angular 2.
TipsIf your changes do not reflect on the browser, then it's probably a caching issue. To resolve that, add the following configuration below under <system.webServer> element in your web.config file:
<caching enabled="false"/>
For ASP.NET Core applications in VStudio 2015, configuring TypeScript is a bit challenging. The tsconfig.json will not be honored because the project file (.xproj) takes precedence. So if you are getting the following TypeScript error or warnings below:
- TS1219 Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option to remove this warning
- TS2307 Cannot find module '@angular/core'.
Until the tooling around TypeScript improves, you can configure TypeScript manually:
Step 1: Right-click project, and Unload Project
Step 2: Right-click the unloaded project, and Edit the .xproj file
Step 3: Add a PropertyGroup node, under the Project node:
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <TypeScriptTarget>ES5</TypeScriptTarget> <TypeScriptJSXEmit>None</TypeScriptJSXEmit> <TypeScriptCompileOnSaveEnabled>True</TypeScriptCompileOnSaveEnabled> <TypeScriptNoImplicitAny>False</TypeScriptNoImplicitAny> <TypeScriptModuleKind>System</TypeScriptModuleKind> <TypeScriptRemoveComments>False</TypeScriptRemoveComments> <TypeScriptOutFile /> <TypeScriptOutDir /> <TypeScriptGeneratesDeclarations>False</TypeScriptGeneratesDeclarations> <TypeScriptNoEmitOnError>True</TypeScriptNoEmitOnError> <TypeScriptSourceMap>True</TypeScriptSourceMap> <TypeScriptMapRoot /> <TypeScriptSourceRoot /> <TypeScriptExperimentalDecorators>True</TypeScriptExperimentalDecorators> <TypeScriptEmitDecoratorMetadata>True</TypeScriptEmitDecoratorMetadata> </PropertyGroup>
Step 4: Right-click the unloaded project and Reload Project
Step 5: Re-build project
We've learned a lot of things in this part of the series, starting from upgrading to Angular 2 RTM down to creating a data-driven Angular2 app from scratch within the context of ASP.NET Core. We learned how to create and communicate with Web API in our Angular 2 app. We've also learned the basics of Angular 2 Master-Detail implementation, Two-Way Data binding and Routing.
Stay tuned for my next article.